diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index db4461e991f22eeaaf92d03e55d5abd1d4946bff..87d067bb5691bf1b1f4d7cf39e5bb6daaf78edbd 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -7,9 +7,9 @@ trigger: batch: true branches: include: - - blazor-wasm - master - release/* + - internal/release/3.* # Run PR validation on all branches pr: @@ -80,18 +80,11 @@ variables: value: test - name: _PublishArgs value: '' -<<<<<<< HEAD -======= - # used for post-build phases, internal builds only - - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - group: DotNet-AspNet-SDLValidation-Params ->>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61 stages: - stage: build displayName: Build jobs: -<<<<<<< HEAD # Code check - ${{ if or(eq(variables['System.TeamProject'], 'public'), in(variables['Build.Reason'], 'PullRequest')) }}: - template: jobs/default-build.yml @@ -116,8 +109,6 @@ stages: publishOnError: true includeForks: true -======= ->>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61 # Build Windows (x64/x86) - template: jobs/default-build.yml parameters: @@ -151,13 +142,12 @@ stages: -arch x64 -pack -all - -NoBuildNative + -buildNative /bl:artifacts/log/build.x64.binlog $(_BuildArgs) $(_InternalRuntimeDownloadArgs) displayName: Build x64 -<<<<<<< HEAD # Build the x86 shared framework # TODO: make it possible to build for one Windows architecture at a time # This is going to actually build x86 native assets. See https://github.com/aspnet/AspNetCore/issues/7196 @@ -183,8 +173,6 @@ stages: $(_InternalRuntimeDownloadArgs) displayName: Build SiteExtension -======= ->>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61 # This runs code-signing on all packages, zips, and jar files as defined in build/CodeSign.targets. If https://github.com/dotnet/arcade/issues/1957 is resolved, # consider running code-signing inline with the other previous steps. # Sign check is disabled because it is run in a separate step below, after installers are built. @@ -212,6 +200,16 @@ stages: /p:PublishInstallerBaseVersion=true displayName: Build Installers + # A few files must also go to the VS package feed. + - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: + - task: NuGetCommand@2 + displayName: Push Visual Studio packages + inputs: + command: push + packagesToPush: 'artifacts/packages/**/VS.Redist.Common.AspNetCore.*.nupkg' + nuGetFeedType: external + publishFeedCredentials: 'DevDiv - VS package feed' + artifacts: - name: Windows_Logs path: artifacts/log/ @@ -220,7 +218,6 @@ stages: - name: Windows_Packages path: artifacts/packages/ -<<<<<<< HEAD # Build Windows ARM - template: jobs/default-build.yml parameters: @@ -515,8 +512,6 @@ stages: parameters: inputName: Linux_musl_arm64 -======= ->>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61 # Test jobs - template: jobs/default-build.yml parameters: @@ -661,7 +656,6 @@ stages: publishOnError: true includeForks: true -<<<<<<< HEAD # Source build - job: Source_Build displayName: 'Test: Linux Source Build' @@ -720,17 +714,23 @@ stages: artifactType: Container parallel: true -======= ->>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61 # Publish to the BAR - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - template: /eng/common/templates/job/publish-build-assets.yml parameters: dependsOn: - Windows_build + - Windows_arm_build + - CodeSign_Xplat_MacOS_x64 + - CodeSign_Xplat_Linux_x64 + - CodeSign_Xplat_Linux_arm + - CodeSign_Xplat_Linux_arm64 + - CodeSign_Xplat_Linux_musl_x64 + - CodeSign_Xplat_Linux_musl_arm64 # In addition to the dependencies above, ensure the build was successful overall. - Linux_Test - MacOS_Test + - Source_Build - Windows_Templates_Test - Windows_Test pool: diff --git a/NuGet.config b/NuGet.config index b632b50bdf521dd13b3c4b18d72dd5fc87062f1a..699d9e10b1ded8169d905aa38f9cb70c60ab7ce6 100644 --- a/NuGet.config +++ b/NuGet.config @@ -3,32 +3,20 @@ <packageSources> <clear /> <!--Begin: Package sources managed by Dependency Flow automation. Do not edit the sources below.--> -<<<<<<< HEAD <add key="darc-pub-dotnet-corefx-8a3ffed" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-corefx-8a3ffed5/nuget/v3/index.json" /> + <add key="darc-pub-dotnet-corefx-66409e3" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-corefx-66409e39/nuget/v3/index.json" /> + <add key="darc-pub-dotnet-blazor-cc44960" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-blazor-cc449601/nuget/v3/index.json" /> <!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.--> <add key="dotnet-core" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" /> <add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" /> <add key="dotnet3.1" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json" /> <add key="dotnet3.1-transport" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json" /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" /> -======= - <add key="darc-pub-dotnet-core-setup-65f04fb" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-core-setup-65f04fb6/nuget/v3/index.json" /> - <add key="darc-pub-dotnet-corefx-0f7f38c" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-corefx-0f7f38c4/nuget/v3/index.json" /> - <!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.--> - <add key="dotnet-core" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" /> <add key="dotnet-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" /> - <add key="aspnet-blazor" value="https://dotnetfeed.blob.core.windows.net/aspnet-blazor/index.json" /> - <add key="aspnet-extensions" value="https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json" /> - <add key="aspnet-entityframeworkcore" value="https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json" /> - <add key="aspnet-aspnetcore-tooling" value="https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json" /> - <add key="aspnet-stable" value="https://dotnetfeed.blob.core.windows.net/dotnet-core-3-1-rtm-014727/index.json" /> - <add key="grpc-nuget-dev" value="https://grpc.jfrog.io/grpc/api/nuget/v3/grpc-nuget-dev" /> - <add key="roslyn" value="https://dotnet.myget.org/F/roslyn/api/v3/index.json" /> + <add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" /> + <add key="dotnet3.1" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1/nuget/v3/index.json" /> + <add key="dotnet3.1-transport" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-transport/nuget/v3/index.json" /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" /> - <add key="aspnetcore-dev" value="https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json" /> - <add key="aspnetcore-tools" value="https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json" /> - <add key="roslyn-tools" value="https://dotnet.myget.org/F/roslyn-tools/api/v3/index.json" /> <add key="blazor-wasm" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet3.1-blazor/nuget/v3/index.json" /> ->>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61 </packageSources> </configuration> diff --git a/eng/Baseline.Designer.props b/eng/Baseline.Designer.props index 78f2952da77e3901a325339d9ab7eb8c6fd21fe4..00294a106fd298bf1ddd1d7a9b74ea9802c762bc 100644 --- a/eng/Baseline.Designer.props +++ b/eng/Baseline.Designer.props @@ -18,6 +18,13 @@ <PropertyGroup Condition=" '$(PackageId)' == 'dotnet-sql-cache' "> <BaselinePackageVersion>3.1.4</BaselinePackageVersion> </PropertyGroup> + <!-- Package: Microsoft.Authentication.WebAssembly.Msal--> + <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.Authentication.WebAssembly.Msal' "> + <BaselinePackageVersion>3.2.0</BaselinePackageVersion> + </PropertyGroup> + <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.Authentication.WebAssembly.Msal' AND '$(TargetFramework)' == 'netstandard2.1' "> + <BaselinePackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="[3.2.0, )" /> + </ItemGroup> <!-- Package: Microsoft.AspNetCore.ApiAuthorization.IdentityServer--> <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.ApiAuthorization.IdentityServer' "> <BaselinePackageVersion>3.1.4</BaselinePackageVersion> @@ -240,6 +247,48 @@ <BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection" Version="[3.1.4, )" /> <BaselinePackageReference Include="Microsoft.JSInterop" Version="[3.1.4, )" /> </ItemGroup> + <!-- Package: Microsoft.AspNetCore.Components.WebAssembly--> + <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly' "> + <BaselinePackageVersion>3.2.0</BaselinePackageVersion> + </PropertyGroup> + <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly' AND '$(TargetFramework)' == 'netstandard2.1' "> + <BaselinePackageReference Include="Microsoft.JSInterop.WebAssembly" Version="[3.2.0, )" /> + <BaselinePackageReference Include="Microsoft.AspNetCore.Components.Web" Version="[3.1.3, )" /> + <BaselinePackageReference Include="Microsoft.Extensions.Configuration.Json" Version="[3.1.3, )" /> + <BaselinePackageReference Include="Microsoft.Extensions.Logging" Version="[3.1.3, )" /> + </ItemGroup> + <!-- Package: Microsoft.AspNetCore.Components.WebAssembly.Build--> + <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Build' "> + <BaselinePackageVersion>3.2.0</BaselinePackageVersion> + </PropertyGroup> + <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Build' AND '$(TargetFramework)' == 'any' "> + <BaselinePackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="[3.2.0, )" /> + </ItemGroup> + <!-- Package: Microsoft.AspNetCore.Components.WebAssembly.Server--> + <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Server' "> + <BaselinePackageVersion>3.2.0</BaselinePackageVersion> + </PropertyGroup> + <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Server' AND '$(TargetFramework)' == 'netcoreapp3.1' " /> + <!-- Package: Microsoft.AspNetCore.Components.WebAssembly.Authentication--> + <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Authentication' "> + <BaselinePackageVersion>3.2.0</BaselinePackageVersion> + </PropertyGroup> + <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.Authentication' AND '$(TargetFramework)' == 'netstandard2.1' "> + <BaselinePackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="[3.1.3, )" /> + <BaselinePackageReference Include="Microsoft.AspNetCore.Components.Web" Version="[3.1.3, )" /> + </ItemGroup> + <!-- Package: Microsoft.AspNetCore.Components.WebAssembly.HttpHandler--> + <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.HttpHandler' "> + <BaselinePackageVersion>3.2.0</BaselinePackageVersion> + </PropertyGroup> + <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Components.WebAssembly.HttpHandler' AND '$(TargetFramework)' == 'netstandard2.1' " /> + <!-- Package: Microsoft.JSInterop.WebAssembly--> + <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.JSInterop.WebAssembly' "> + <BaselinePackageVersion>3.2.0</BaselinePackageVersion> + </PropertyGroup> + <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.JSInterop.WebAssembly' AND '$(TargetFramework)' == 'netstandard2.1' "> + <BaselinePackageReference Include="Microsoft.JSInterop" Version="[3.1.3, )" /> + </ItemGroup> <!-- Package: Microsoft.AspNetCore.ConcurrencyLimiter--> <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.ConcurrencyLimiter' "> <BaselinePackageVersion>3.1.4</BaselinePackageVersion> diff --git a/eng/Baseline.xml b/eng/Baseline.xml index a768d1d7645e4265cf4681664318321d57200931..ee094c82d5ea7c8928f55c2244d5ad3f3088371a 100644 --- a/eng/Baseline.xml +++ b/eng/Baseline.xml @@ -8,6 +8,7 @@ Update this list when preparing for a new patch. <Package Id="AspNetCoreRuntime.3.0.x64" Version="3.0.3" /> <Package Id="AspNetCoreRuntime.3.0.x86" Version="3.0.3" /> <Package Id="dotnet-sql-cache" Version="3.1.4" /> + <Package Id="Microsoft.Authentication.WebAssembly.Msal" Version="3.2.0" /> <Package Id="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="3.1.4" /> <Package Id="Microsoft.AspNetCore.App.Runtime.win-x64" Version="3.1.4" /> <Package Id="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="3.1.4" /> @@ -36,6 +37,12 @@ Update this list when preparing for a new patch. <Package Id="Microsoft.AspNetCore.Components.Authorization" Version="3.1.4" /> <Package Id="Microsoft.AspNetCore.Components.Forms" Version="3.1.4" /> <Package Id="Microsoft.AspNetCore.Components.Web" Version="3.1.4" /> + <Package Id="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" /> + <Package Id="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" /> + <Package Id="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="3.2.0" /> + <Package Id="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="3.2.0" /> + <Package Id="Microsoft.AspNetCore.Components.WebAssembly.HttpHandler" Version="3.2.0" /> + <Package Id="Microsoft.JSInterop.WebAssembly" Version="3.2.0" /> <Package Id="Microsoft.AspNetCore.ConcurrencyLimiter" Version="3.1.4" /> <Package Id="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.4" /> <Package Id="Microsoft.AspNetCore.Cryptography.Internal" Version="3.1.4" /> diff --git a/eng/Build.props b/eng/Build.props index 6ba4d0eda6010369d3ec4d304c0693cd151ed0a8..95b9636667e168cc6ca10a616cd22a43acb84a81 100644 --- a/eng/Build.props +++ b/eng/Build.props @@ -34,8 +34,8 @@ $(RepoRoot)src\Installers\**\*.*proj; $(RepoRoot)src\SignalR\clients\ts\**\node_modules\**\*.*proj; $(RepoRoot)src\Components\Web.JS\node_modules\**\*.*proj; - $(RepoRoot)src\Components\Blazor\Build\testassets\**\*.*proj; - $(RepoRoot)src\ProjectTemplates\BlazorWasm.ProjectTemplates\content\**\*.csproj; + $(RepoRoot)src\Components\WebAssembly\Build\testassets\**\*.csproj; + $(RepoRoot)src\ProjectTemplates\ComponentsWebAssembly.ProjectTemplates\content\**\*.csproj; $(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.csproj; $(RepoRoot)src\ProjectTemplates\Web.ProjectTemplates\content\**\*.fsproj; $(RepoRoot)src\ProjectTemplates\Web.Spa.ProjectTemplates\content\**\*.csproj; @@ -50,11 +50,6 @@ " /> </ItemGroup> - <PropertyGroup> - <!-- For the Blazor WASM branch, only build a subset of projects --> - <ProjectToBuild>$(RepoRoot)src\Components\**\*.csproj;$(RepoRoot)src\ProjectTemplates\**\*.csproj</ProjectToBuild> - </PropertyGroup> - <Choose> <!-- Project selection can be overridden on the command line by passing in -projects --> <When Condition="'$(ProjectToBuild)' != ''"> diff --git a/eng/Dependencies.props b/eng/Dependencies.props index 73ffdde39f7b76b85b6321a5c8b87e5a01ceec08..22235e50c4c9014782ea9fc9b1d5953e8cc2bfb2 100644 --- a/eng/Dependencies.props +++ b/eng/Dependencies.props @@ -92,6 +92,7 @@ and are generated based on the last package release. <LatestPackageReference Include="System.Drawing.Common" Version="$(SystemDrawingCommonPackageVersion)" /> <LatestPackageReference Include="System.IO.Pipelines" Version="$(SystemIOPipelinesPackageVersion)" /> <LatestPackageReference Include="System.Net.Http" Version="$(SystemNetHttpPackageVersion)" /> + <LatestPackageReference Include="System.Net.Http.Json" Version="$(SystemNetHttpJsonPackageVersion)" /> <LatestPackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataPackageVersion)" /> <LatestPackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)" /> <LatestPackageReference Include="System.Security.Cryptography.Cng" Version="$(SystemSecurityCryptographyCngPackageVersion)" /> @@ -122,7 +123,7 @@ and are generated based on the last package release. <LatestPackageReference Include="Microsoft.AspNetCore.AzureAppServices.SiteExtension.2.1" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension21PackageVersion)" /> <LatestPackageReference Include="Microsoft.AspNetCore.AzureAppServices.SiteExtension.2.2" Version="$(MicrosoftAspNetCoreAzureAppServicesSiteExtension22PackageVersion)" /> <LatestPackageReference Include="Microsoft.AspNetCore.BenchmarkRunner.Sources" Version="$(MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion)" /> - <LatestPackageReference Include="Microsoft.AspNetCore.Blazor.Mono" Version="$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" /> + <LatestPackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="$(MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion)" /> <LatestPackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" /> <LatestPackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="$(MicrosoftEntityFrameworkCoreInMemoryPackageVersion)" /> <LatestPackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="$(MicrosoftEntityFrameworkCoreRelationalPackageVersion)" /> diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index dedf038e0b51e572a6ee8bf70fedb4d1fd745071..c0ecfb2b07699b0024410cc2667d615489db7848 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -55,12 +55,14 @@ <ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" ProjectPath="$(RepoRoot)src\SignalR\common\Protocols.NewtonsoftJson\src\Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Specification.Tests" ProjectPath="$(RepoRoot)src\SignalR\server\Specification.Tests\src\Microsoft.AspNetCore.SignalR.Specification.Tests.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" ProjectPath="$(RepoRoot)src\SignalR\server\StackExchangeRedis\src\Microsoft.AspNetCore.SignalR.StackExchangeRedis.csproj" /> - <ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor" ProjectPath="$(RepoRoot)src\Components\Blazor\Blazor\src\Microsoft.AspNetCore.Blazor.csproj" /> - <ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Build" ProjectPath="$(RepoRoot)src\Components\Blazor\Build\src\Microsoft.AspNetCore.Blazor.Build.csproj" /> - <ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.HttpClient" ProjectPath="$(RepoRoot)src\Components\Blazor\Http\src\Microsoft.AspNetCore.Blazor.HttpClient.csproj" /> - <ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Server" ProjectPath="$(RepoRoot)src\Components\Blazor\Server\src\Microsoft.AspNetCore.Blazor.Server.csproj" /> - <ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" ProjectPath="$(RepoRoot)src\Components\Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj" /> <ProjectReferenceProvider Include="Ignitor" ProjectPath="$(RepoRoot)src\Components\Ignitor\src\Ignitor.csproj" /> + <ProjectReferenceProvider Include="Microsoft.Authentication.WebAssembly.Msal" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Authentication.Msal\src\Microsoft.Authentication.WebAssembly.Msal.csproj" /> + <ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Build" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Build\src\Microsoft.AspNetCore.Components.WebAssembly.Build.csproj" /> + <ProjectReferenceProvider Include="blazor-brotli" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Compression\src\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj" /> + <ProjectReferenceProvider Include="Microsoft.JSInterop.WebAssembly" ProjectPath="$(RepoRoot)src\Components\WebAssembly\JSInterop\src\Microsoft.JSInterop.WebAssembly.csproj" /> + <ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Server" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Server\src\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj" /> + <ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" ProjectPath="$(RepoRoot)src\Components\WebAssembly\WebAssembly.Authentication\src\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj" /> + <ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly" ProjectPath="$(RepoRoot)src\Components\WebAssembly\WebAssembly\src\Microsoft.AspNetCore.Components.WebAssembly.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore" ProjectPath="$(RepoRoot)src\DefaultBuilder\src\Microsoft.AspNetCore.csproj" RefProjectPath="$(RepoRoot)src\DefaultBuilder\ref\Microsoft.AspNetCore.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore.DataProtection.Abstractions" ProjectPath="$(RepoRoot)src\DataProtection\Abstractions\src\Microsoft.AspNetCore.DataProtection.Abstractions.csproj" RefProjectPath="$(RepoRoot)src\DataProtection\Abstractions\ref\Microsoft.AspNetCore.DataProtection.Abstractions.csproj" /> <ProjectReferenceProvider Include="Microsoft.AspNetCore.Cryptography.Internal" ProjectPath="$(RepoRoot)src\DataProtection\Cryptography.Internal\src\Microsoft.AspNetCore.Cryptography.Internal.csproj" RefProjectPath="$(RepoRoot)src\DataProtection\Cryptography.Internal\ref\Microsoft.AspNetCore.Cryptography.Internal.csproj" /> diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 037cb728dcafd90eda18a3a80330a7f637dd9334..580e6b3b36bb6c496cb67d5a9d9bb6dd1c1885ec 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -9,9 +9,13 @@ --> <Dependencies> <ProductDependencies> - <Dependency Name="Microsoft.AspNetCore.Blazor.Mono" Version="3.1.0-preview4.19605.1" Pinned="true"> - <Uri>https://github.com/aspnet/Blazor</Uri> - <Sha>7868699de745fd30a654c798a99dc541b77b95c0</Sha> + <Dependency Name="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="3.2.0"> + <Uri>https://github.com/dotnet/blazor</Uri> + <Sha>cc449601d638ffaab58ae9487f0fd010bb178a12</Sha> + </Dependency> + <Dependency Name="System.Net.Http.Json" Version="3.2.0"> + <Uri>https://github.com/dotnet/corefx</Uri> + <Sha>66409e392d64ed96e5d3a5fda712d9baf51196ed</Sha> </Dependency> <Dependency Name="Microsoft.AspNetCore.Razor.Language" Version="3.1.4"> <Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore-tooling</Uri> diff --git a/eng/Versions.props b/eng/Versions.props index c43b1c1855a0a7b4e6a0a9fa3a3d87867903affe..07f4eaf7bac39705146c9b0ca13bcbd63d3ec561 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -10,6 +10,11 @@ <AspNetCoreMinorVersion>1</AspNetCoreMinorVersion> <AspNetCorePatchVersion>5</AspNetCorePatchVersion> <PreReleasePreviewNumber>0</PreReleasePreviewNumber> + + <ComponentsWebAssemblyMajorVersion>3</ComponentsWebAssemblyMajorVersion> + <ComponentsWebAssemblyMinorVersion>2</ComponentsWebAssemblyMinorVersion> + <ComponentsWebAssemblyPatchVersion>1</ComponentsWebAssemblyPatchVersion> + <!-- When StabilizePackageVersion is set to 'true', this branch will produce stable outputs for 'Shipping' packages --> @@ -19,9 +24,6 @@ <IncludePreReleaseLabelInPackageVersion Condition=" '$(DotNetFinalVersionKind)' == 'release' ">false</IncludePreReleaseLabelInPackageVersion> <PreReleaseVersionLabel>servicing</PreReleaseVersionLabel> <PreReleaseBrandingLabel>Servicing</PreReleaseBrandingLabel> - <!-- Blazor Client packages will not RTM with 3.1 --> - <BlazorClientPreReleasePreviewNumber>4</BlazorClientPreReleasePreviewNumber> - <BlazorClientPreReleaseVersionLabel>preview$(BlazorClientPreReleasePreviewNumber)</BlazorClientPreReleaseVersionLabel> <AspNetCoreMajorMinorVersion>$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion)</AspNetCoreMajorMinorVersion> <!-- The following property may need to be updated if ingesting new versions of Extensions.Refs package. The package override version is used to create PackageOverrides.txt in the targeting pack. --> <MicrosoftInternalExtensionsRefsPackageOverrideVersion>3.1.0</MicrosoftInternalExtensionsRefsPackageOverrideVersion> @@ -30,6 +32,7 @@ <!-- Servicing builds have different characteristics for the way dependencies, framework references, and versions are handled. --> <IsServicingBuild Condition=" '$(PreReleaseVersionLabel)' == 'servicing' ">true</IsServicingBuild> <VersionPrefix>$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion).$(AspNetCorePatchVersion)</VersionPrefix> + <ComponentsWebAssemblyVersionPrefix>$(ComponentsWebAssemblyMajorVersion).$(ComponentsWebAssemblyMinorVersion).$(ComponentsWebAssemblyPatchVersion)</ComponentsWebAssemblyVersionPrefix> <!-- TargetingPackVersionPrefix is used by projects, like .deb and .rpm, which use slightly different version formats. --> <TargetingPackVersionPrefix>$(VersionPrefix)</TargetingPackVersionPrefix> <!-- Targeting packs do not produce patch versions in servicing builds. No API changes are allowed in patches. --> @@ -92,12 +95,13 @@ <SystemServiceProcessServiceControllerPackageVersion>4.7.0</SystemServiceProcessServiceControllerPackageVersion> <SystemTextEncodingsWebPackageVersion>4.7.1</SystemTextEncodingsWebPackageVersion> <SystemTextJsonPackageVersion>4.7.2</SystemTextJsonPackageVersion> + <SystemNetHttpJsonPackageVersion>3.2.0</SystemNetHttpJsonPackageVersion> <SystemThreadingChannelsPackageVersion>4.7.1</SystemThreadingChannelsPackageVersion> <SystemWindowsExtensionsPackageVersion>4.7.0</SystemWindowsExtensionsPackageVersion> <!-- Only listed explicitly to workaround https://github.com/dotnet/cli/issues/10528 --> <MicrosoftNETCorePlatformsPackageVersion>3.1.1</MicrosoftNETCorePlatformsPackageVersion> <!-- Packages from aspnet/Blazor --> - <MicrosoftAspNetCoreBlazorMonoPackageVersion>3.1.0-preview4.19605.1</MicrosoftAspNetCoreBlazorMonoPackageVersion> + <MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion>3.2.0</MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion> <!-- Packages from aspnet/Extensions --> <InternalAspNetCoreAnalyzersPackageVersion>3.1.4-servicing.20221.11</InternalAspNetCoreAnalyzersPackageVersion> <MicrosoftAspNetCoreAnalyzerTestingPackageVersion>3.1.4-servicing.20221.11</MicrosoftAspNetCoreAnalyzerTestingPackageVersion> diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index 29d76cd3bed7380033522ad9f74d294fb367254b..92a053bd16b46e3059135daae0f930799030eb64 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -257,7 +257,7 @@ function InitializeVisualStudioMSBuild([bool]$install, [object]$vsRequirements = 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]) + $msbuildVersion = [Version]::new((Get-Item $msbuildCmd.Path).VersionInfo.ProductVersion.Split(@('-', '+'))[0]) if ($msbuildVersion -ge $vsMinVersion) { return $global:_MSBuildExe = $msbuildCmd.Path diff --git a/eng/scripts/CodeCheck.ps1 b/eng/scripts/CodeCheck.ps1 index a7baf079c824bc44fb73fdd4e3df190c59dfef81..072f55fe21962edba1690a95c85354d3ee68d906 100644 --- a/eng/scripts/CodeCheck.ps1 +++ b/eng/scripts/CodeCheck.ps1 @@ -170,11 +170,10 @@ try { & $PSScriptRoot\GenerateReferenceAssemblies.ps1 -ci:$ci } - # Temporarily disable package baseline generation while we stage for publishing - # Write-Host "Re-generating package baselines" - # Invoke-Block { - # & dotnet run -p "$repoRoot/eng/tools/BaselineGenerator/" - # } + Write-Host "Re-generating package baselines" + Invoke-Block { + & dotnet run -p "$repoRoot/eng/tools/BaselineGenerator/" + } Write-Host "Run git diff to check for pending changes" diff --git a/global.json b/global.json index e0882dd64d6365b1fafb69e4f6c0c65afdf3a83e..974708a61113ed99567d82ef9b756719cc054c6f 100644 --- a/global.json +++ b/global.json @@ -1,16 +1,9 @@ { "sdk": { -<<<<<<< HEAD "version": "3.1.103" }, "tools": { "dotnet": "3.1.103", -======= - "version": "3.1.100" - }, - "tools": { - "dotnet": "3.1.100", ->>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61 "runtimes": { "dotnet/x64": [ "$(MicrosoftNETCoreAppInternalPackageVersion)" @@ -32,12 +25,7 @@ }, "msbuild-sdks": { "Yarn.MSBuild": "1.15.2", -<<<<<<< HEAD "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.20213.4", "Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.20213.4" -======= - "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.19577.5", - "Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.19577.5" ->>>>>>> bbafecc0535e1de3264845e51ea8b3d18eb3ca61 } } diff --git a/src/Components/Blazor/Blazor.Version.props b/src/Components/Blazor/Blazor.Version.props deleted file mode 100644 index 123a94c1d7729a447a72669198d96782f7dbd56e..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Blazor.Version.props +++ /dev/null @@ -1,8 +0,0 @@ -<Project> - <PropertyGroup> - <!-- Override version labels --> - <VersionPrefix>3.2.0</VersionPrefix> - <PreReleaseVersionLabel>preview1</PreReleaseVersionLabel> - <DotNetFinalVersionKind /> - </PropertyGroup> -</Project> \ No newline at end of file diff --git a/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.1.cs b/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.1.cs deleted file mode 100644 index eb33362975188b0867092993bf5e66d5e0d4708a..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Blazor/ref/Microsoft.AspNetCore.Blazor.netstandard2.1.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Microsoft.AspNetCore.Blazor -{ - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] - public static partial class JSInteropMethods - { - [Microsoft.JSInterop.JSInvokableAttribute("NotifyLocationChanged")] - public static void NotifyLocationChanged(string uri, bool isInterceptedLink) { } - } -} -namespace Microsoft.AspNetCore.Blazor.Hosting -{ - [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] - public readonly partial struct RootComponentMapping - { - private readonly object _dummy; - public RootComponentMapping(System.Type componentType, string selector) { throw null; } - public System.Type ComponentType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public string Selector { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - } - public partial class RootComponentMappingCollection : System.Collections.ObjectModel.Collection<Microsoft.AspNetCore.Blazor.Hosting.RootComponentMapping> - { - public RootComponentMappingCollection() { } - public void Add(System.Type componentType, string selector) { } - public void AddRange(System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Blazor.Hosting.RootComponentMapping> items) { } - public void Add<TComponent>(string selector) where TComponent : Microsoft.AspNetCore.Components.IComponent { } - } - public sealed partial class WebAssemblyHost : System.IAsyncDisposable - { - internal WebAssemblyHost() { } - public Microsoft.Extensions.Configuration.IConfiguration Configuration { get { throw null; } } - public System.IServiceProvider Services { get { throw null; } } - [System.Diagnostics.DebuggerStepThroughAttribute] - public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } - public System.Threading.Tasks.Task RunAsync() { throw null; } - } - public sealed partial class WebAssemblyHostBuilder - { - internal WebAssemblyHostBuilder() { } - public Microsoft.Extensions.Configuration.IConfigurationBuilder Configuration { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Blazor.Hosting.RootComponentMappingCollection RootComponents { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.Extensions.DependencyInjection.IServiceCollection Services { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } - public Microsoft.AspNetCore.Blazor.Hosting.WebAssemblyHost Build() { throw null; } - public static Microsoft.AspNetCore.Blazor.Hosting.WebAssemblyHostBuilder CreateDefault(string[] args = null) { throw null; } - } -} -namespace Microsoft.AspNetCore.Blazor.Http -{ - public enum FetchCredentialsOption - { - Omit = 0, - SameOrigin = 1, - Include = 2, - } - public static partial class WebAssemblyHttpMessageHandlerOptions - { - public static Microsoft.AspNetCore.Blazor.Http.FetchCredentialsOption DefaultCredentials { get { throw null; } set { } } - } -} -namespace Microsoft.AspNetCore.Blazor.Rendering -{ - public static partial class WebAssemblyEventDispatcher - { - [Microsoft.JSInterop.JSInvokableAttribute("DispatchEvent")] - public static System.Threading.Tasks.Task DispatchEvent(Microsoft.AspNetCore.Components.RenderTree.WebEventDescriptor eventDescriptor, string eventArgsJson) { throw null; } - } -} diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilder.cs b/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilder.cs deleted file mode 100644 index 1d1f05dce7c269ef13d2f25588c290f2a3fa6eef..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHostBuilder.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using Microsoft.AspNetCore.Blazor.Services; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Routing; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; -using Microsoft.JSInterop; - -namespace Microsoft.AspNetCore.Blazor.Hosting -{ - /// <summary> - /// A builder for configuring and creating a <see cref="WebAssemblyHost"/>. - /// </summary> - public sealed class WebAssemblyHostBuilder - { - /// <summary> - /// Creates an instance of <see cref="WebAssemblyHostBuilder"/> using the most common - /// conventions and settings. - /// </summary> - /// <param name="args">The argument passed to the application's main method.</param> - /// <returns>A <see cref="WebAssemblyHostBuilder"/>.</returns> - public static WebAssemblyHostBuilder CreateDefault(string[] args = default) - { - // We don't use the args for anything right now, but we want to accept them - // here so that it shows up this way in the project templates. - args ??= Array.Empty<string>(); - var builder = new WebAssemblyHostBuilder(); - - // Right now we don't have conventions or behaviors that are specific to this method - // however, making this the default for the template allows us to add things like that - // in the future, while giving `new WebAssemblyHostBuilder` as an opt-out of opinionated - // settings. - return builder; - } - - /// <summary> - /// Creates an instance of <see cref="WebAssemblyHostBuilder"/> with the minimal configuration. - /// </summary> - private WebAssemblyHostBuilder() - { - // Private right now because we don't have much reason to expose it. This can be exposed - // in the future if we want to give people a choice between CreateDefault and something - // less opinionated. - Configuration = new ConfigurationBuilder(); - RootComponents = new RootComponentMappingCollection(); - Services = new ServiceCollection(); - - InitializeDefaultServices(); - } - - /// <summary> - /// Gets an <see cref="IConfigurationBuilder"/> that can be used to customize the application's - /// configuration sources. - /// </summary> - public IConfigurationBuilder Configuration { get; } - - /// <summary> - /// Gets the collection of root component mappings configured for the application. - /// </summary> - public RootComponentMappingCollection RootComponents { get; } - - /// <summary> - /// Gets the service collection. - /// </summary> - public IServiceCollection Services { get; } - - /// <summary> - /// Builds a <see cref="WebAssemblyHost"/> instance based on the configuration of this builder. - /// </summary> - /// <returns>A <see cref="WebAssemblyHost"/> object.</returns> - public WebAssemblyHost Build() - { - // Intentionally overwrite configuration with the one we're creating. - var configuration = Configuration.Build(); - Services.AddSingleton<IConfiguration>(configuration); - - // A Blazor application always runs in a scope. Since we want to make it possible for the user - // to configure services inside *that scope* inside their startup code, we create *both* the - // service provider and the scope here. - var services = Services.BuildServiceProvider(); - var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope(); - - return new WebAssemblyHost(services, scope, configuration, RootComponents.ToArray()); - } - - private void InitializeDefaultServices() - { - Services.AddSingleton<IJSRuntime>(WebAssemblyJSRuntime.Instance); - Services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance); - Services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance); - Services.AddSingleton<ILoggerFactory, WebAssemblyLoggerFactory>(); - Services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(WebAssemblyConsoleLogger<>))); - Services.AddSingleton<HttpClient>(s => - { - // Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it. - var navigationManager = s.GetRequiredService<NavigationManager>(); - return new HttpClient - { - BaseAddress = new Uri(navigationManager.BaseUri) - }; - }); - } - } -} diff --git a/src/Components/Blazor/Blazor/src/Http/WebAssemblyHttpMessageHandlerOptions.cs b/src/Components/Blazor/Blazor/src/Http/WebAssemblyHttpMessageHandlerOptions.cs deleted file mode 100644 index bc46e4e527ac55c4c86f2b1a6ace17904f04edf5..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Blazor/src/Http/WebAssemblyHttpMessageHandlerOptions.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Reflection; - -namespace Microsoft.AspNetCore.Blazor.Http -{ - /// <summary> - /// Configures options for the WebAssembly HTTP message handler. - /// </summary> - public static class WebAssemblyHttpMessageHandlerOptions - { - /// <summary> - /// Gets or sets the default value of the 'credentials' option on outbound HTTP requests. - /// Defaults to <see cref="FetchCredentialsOption.SameOrigin"/>. - /// </summary> - public static FetchCredentialsOption DefaultCredentials - { - get - { - var valueString = MonoDefaultCredentialsGetter.Value(); - var result = default(FetchCredentialsOption); - if (valueString != null) - { - Enum.TryParse(valueString, out result); - } - return result; - } - - set - { - MonoDefaultCredentialsSetter.Value(value.ToString()); - } - } - - static Func<Type> MonoWasmHttpMessageHandlerType = () - => Assembly.Load("WebAssembly.Net.Http") - .GetType("WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler"); - - static Func<Type> MonoFetchCredentialsOptionType = () - => Assembly.Load("WebAssembly.Net.Http") - .GetType("WebAssembly.Net.Http.HttpClient.FetchCredentialsOption"); - - static Lazy<PropertyInfo> MonoDefaultCredentialsProperty = new Lazy<PropertyInfo>( - () => MonoWasmHttpMessageHandlerType()?.GetProperty("DefaultCredentials", BindingFlags.Public | BindingFlags.Static)); - - static Lazy<Func<string>> MonoDefaultCredentialsGetter = new Lazy<Func<string>>(() => - { - return () => MonoDefaultCredentialsProperty.Value?.GetValue(null).ToString(); - }); - - static Lazy<Action<string>> MonoDefaultCredentialsSetter = new Lazy<Action<string>>(() => - { - var fetchCredentialsOptionsType = MonoFetchCredentialsOptionType(); - return value => MonoDefaultCredentialsProperty.Value?.SetValue(null, Enum.Parse(fetchCredentialsOptionsType, value)); - }); - } -} diff --git a/src/Components/Blazor/Blazor/src/JSInteropMethods.cs b/src/Components/Blazor/Blazor/src/JSInteropMethods.cs deleted file mode 100644 index 239dbb49529c0f83263e14622962972bed623179..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Blazor/src/JSInteropMethods.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.ComponentModel; -using Microsoft.AspNetCore.Blazor.Services; -using Microsoft.JSInterop; - -namespace Microsoft.AspNetCore.Blazor -{ - /// <summary> - /// Contains methods called by interop. Intended for framework use only, not supported for use in application - /// code. - /// </summary> - [EditorBrowsable(EditorBrowsableState.Never)] - public static class JSInteropMethods - { - /// <summary> - /// For framework use only. - /// </summary> - [JSInvokable(nameof(NotifyLocationChanged))] - public static void NotifyLocationChanged(string uri, bool isInterceptedLink) - { - WebAssemblyNavigationManager.Instance.SetLocation(uri, isInterceptedLink); - } - } -} diff --git a/src/Components/Blazor/Blazor/src/Microsoft.AspNetCore.Blazor.csproj b/src/Components/Blazor/Blazor/src/Microsoft.AspNetCore.Blazor.csproj deleted file mode 100644 index 3a4e98a8b7e21b94394ca9c0fd84a8235020fd7a..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Blazor/src/Microsoft.AspNetCore.Blazor.csproj +++ /dev/null @@ -1,23 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>netstandard2.1</TargetFramework> - <Description>Build client-side single-page applications (SPAs) with Blazor running under WebAssembly.</Description> - <IsShippingPackage>false</IsShippingPackage> - </PropertyGroup> - - <ItemGroup> - <Reference Include="Mono.WebAssembly.Interop" /> - <Reference Include="Microsoft.AspNetCore.Components.Web" /> - <Reference Include="Microsoft.Extensions.Configuration" /> - </ItemGroup> - - <ItemGroup> - <Compile Include="$(ComponentsSharedSourceRoot)src\BrowserNavigationManagerInterop.cs" /> - <Compile Include="$(ComponentsSharedSourceRoot)src\JsonSerializerOptionsProvider.cs" /> - <Compile Include="$(ComponentsSharedSourceRoot)src\WebEventData.cs" /> - - <Compile Include="$(ComponentsSharedSourceRoot)src\ElementReferenceJsonConverter.cs" /> - </ItemGroup> - -</Project> diff --git a/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs b/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs deleted file mode 100644 index c86c1cf30beb47d3066a5c07656cf6ef60ba04f3..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Blazor/src/Services/WebAssemblyConsoleLogger.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Blazor.Services -{ - internal class WebAssemblyConsoleLogger<T> : ILogger<T>, ILogger - { - public IDisposable BeginScope<TState>(TState state) - { - return NoOpDisposable.Instance; - } - - public bool IsEnabled(LogLevel logLevel) - { - return logLevel >= LogLevel.Warning; - } - - public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) - { - var formattedMessage = formatter(state, exception); - Console.WriteLine($"[{logLevel}] {formattedMessage}"); - } - - private class NoOpDisposable : IDisposable - { - public static NoOpDisposable Instance = new NoOpDisposable(); - - public void Dispose() { } - } - } -} diff --git a/src/Components/Blazor/Blazor/src/Services/WebAssemblyJSRuntime.cs b/src/Components/Blazor/Blazor/src/Services/WebAssemblyJSRuntime.cs deleted file mode 100644 index c5404c256f05bce2bb7e91118eeaec08813b50c6..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Blazor/src/Services/WebAssemblyJSRuntime.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Components; -using Mono.WebAssembly.Interop; - -namespace Microsoft.AspNetCore.Blazor.Services -{ - internal sealed class WebAssemblyJSRuntime : MonoWebAssemblyJSRuntime - { - private static readonly WebAssemblyJSRuntime _instance = new WebAssemblyJSRuntime(); - private static bool _initialized; - - public WebAssemblyJSRuntime() - { - JsonSerializerOptions.Converters.Add(new ElementReferenceJsonConverter()); - } - - public static WebAssemblyJSRuntime Instance - { - get - { - if (!_initialized) - { - // This is executing in MonoWASM. Consequently we do not to have concern ourselves with thread safety. - _initialized = true; - Initialize(_instance); - } - - return _instance; - } - } - } -} diff --git a/src/Components/Blazor/Blazor/src/Services/WebAssemblyLoggerFactory.cs b/src/Components/Blazor/Blazor/src/Services/WebAssemblyLoggerFactory.cs deleted file mode 100644 index 73458387e74a197c380faef05ec57018294d03c8..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Blazor/src/Services/WebAssemblyLoggerFactory.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Blazor.Services -{ - internal class WebAssemblyLoggerFactory : ILoggerFactory - { - public void AddProvider(ILoggerProvider provider) - { - // No-op - } - - public ILogger CreateLogger(string categoryName) - => new WebAssemblyConsoleLogger<object>(); - - public void Dispose() - { - // No-op - } - } -} diff --git a/src/Components/Blazor/Blazor/test/Hosting/WebAssemblyHostBuilderTest.cs b/src/Components/Blazor/Blazor/test/Hosting/WebAssemblyHostBuilderTest.cs deleted file mode 100644 index 77b6583d260e303c8781361750266c9577817507..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Blazor/test/Hosting/WebAssemblyHostBuilderTest.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Text; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Routing; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.JSInterop; -using Xunit; - -namespace Microsoft.AspNetCore.Blazor.Hosting -{ - public class WebAssemblyHostBuilderTest - { - [Fact] - public void Build_AllowsConfiguringConfiguration() - { - // Arrange - var builder = WebAssemblyHostBuilder.CreateDefault(); - - builder.Configuration.AddInMemoryCollection(new[] - { - new KeyValuePair<string, string>("key", "value"), - }); - - // Act - var host = builder.Build(); - - // Assert - Assert.Equal("value", host.Configuration["key"]); - } - - [Fact] - public void Build_AllowsConfiguringServices() - { - // Arrange - var builder = WebAssemblyHostBuilder.CreateDefault(); - - // This test also verifies that we create a scope. - builder.Services.AddScoped<StringBuilder>(); - - // Act - var host = builder.Build(); - - // Assert - Assert.NotNull(host.Services.GetRequiredService<StringBuilder>()); - } - - [Fact] - public void Build_AddsConfigurationToServices() - { - // Arrange - var builder = WebAssemblyHostBuilder.CreateDefault(); - - builder.Configuration.AddInMemoryCollection(new[] - { - new KeyValuePair<string, string>("key", "value"), - }); - - // Act - var host = builder.Build(); - - // Assert - var configuration = host.Services.GetRequiredService<IConfiguration>(); - Assert.Equal("value", configuration["key"]); - } - - private static IReadOnlyList<Type> DefaultServiceTypes - { - get - { - return new Type[] - { - typeof(IJSRuntime), - typeof(NavigationManager), - typeof(INavigationInterception), - typeof(ILoggerFactory), - typeof(HttpClient), - typeof(ILogger<>), - }; - } - } - - [Fact] - public void Constructor_AddsDefaultServices() - { - // Arrange & Act - var builder = WebAssemblyHostBuilder.CreateDefault(); - - // Assert - Assert.Equal(DefaultServiceTypes.Count, builder.Services.Count); - foreach (var type in DefaultServiceTypes) - { - Assert.Single(builder.Services, d => d.ServiceType == type); - } - } - } -} diff --git a/src/Components/Blazor/Blazor/test/Microsoft.AspNetCore.Blazor.Tests.csproj b/src/Components/Blazor/Blazor/test/Microsoft.AspNetCore.Blazor.Tests.csproj deleted file mode 100644 index 40c5a5b7025edfeef5469861f33c580d6c100bd4..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Blazor/test/Microsoft.AspNetCore.Blazor.Tests.csproj +++ /dev/null @@ -1,17 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> - <!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. --> - <CompileUsingReferenceAssemblies>false</CompileUsingReferenceAssemblies> - </PropertyGroup> - - <ItemGroup> - <Reference Include="Microsoft.AspNetCore.Blazor" /> - <!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. --> - <Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" /> - <!-- Avoid MSB3277 warnings due to dependencies brought in through Microsoft.AspNetCore.Blazor targeting netstandard2.0. --> - <Reference Include="System.Text.Json" /> - </ItemGroup> - -</Project> diff --git a/src/Components/Blazor/Build/src/Properties/AssemblyInfo.cs b/src/Components/Blazor/Build/src/Properties/AssemblyInfo.cs deleted file mode 100644 index aac42c25ccbdc758ce224c149f3c8ea715571da6..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/src/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Blazor.Build.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs b/src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs deleted file mode 100644 index 1984de0a5798c737b342f5cbbaa07be3ca50a2ed..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/src/Tasks/GenerateBlazorBootJson.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.Serialization.Json; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Microsoft.AspNetCore.Blazor.Build -{ - public class GenerateBlazorBootJson : Task - { - [Required] - public string AssemblyPath { get; set; } - - [Required] - public ITaskItem[] References { get; set; } - - [Required] - public bool LinkerEnabled { get; set; } - - [Required] - public string OutputPath { get; set; } - - public override bool Execute() - { - var entryAssemblyName = AssemblyName.GetAssemblyName(AssemblyPath).Name; - var assemblies = References.Select(GetUriPath).OrderBy(c => c, StringComparer.Ordinal).ToArray(); - - using var fileStream = File.Create(OutputPath); - WriteBootJson(fileStream, entryAssemblyName, assemblies, LinkerEnabled); - - return true; - - static string GetUriPath(ITaskItem item) - { - var outputPath = item.GetMetadata("RelativeOutputPath"); - if (string.IsNullOrEmpty(outputPath)) - { - outputPath = Path.GetFileName(item.ItemSpec); - } - - return outputPath.Replace('\\', '/'); - } - } - - internal static void WriteBootJson(Stream stream, string entryAssemblyName, string[] assemblies, bool linkerEnabled) - { - var data = new BootJsonData - { - entryAssembly = entryAssemblyName, - assemblies = assemblies, - linkerEnabled = linkerEnabled, - }; - - var serializer = new DataContractJsonSerializer(typeof(BootJsonData)); - serializer.WriteObject(stream, data); - } - - /// <summary> - /// Defines the structure of a Blazor boot JSON file - /// </summary> -#pragma warning disable IDE1006 // Naming Styles - public class BootJsonData - { - /// <summary> - /// Gets the name of the assembly with the application entry point - /// </summary> - public string entryAssembly { get; set; } - - /// <summary> - /// Gets the closure of assemblies to be loaded by Blazor WASM. This includes the application entry assembly. - /// </summary> - public string[] assemblies { get; set; } - - /// <summary> - /// Gets a value that determines if the linker is enabled. - /// </summary> - public bool linkerEnabled { get; set; } - } -#pragma warning restore IDE1006 // Naming Styles - } -} diff --git a/src/Components/Blazor/Build/src/build/netstandard1.0/Microsoft.AspNetCore.Blazor.Build.props b/src/Components/Blazor/Build/src/build/netstandard1.0/Microsoft.AspNetCore.Blazor.Build.props deleted file mode 100644 index f20d90334cc55b41bc208b4e9447713427653e3b..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/src/build/netstandard1.0/Microsoft.AspNetCore.Blazor.Build.props +++ /dev/null @@ -1,3 +0,0 @@ -<Project> - <Import Project="$(MSBuildThisFileDirectory)..\..\targets\All.props" /> -</Project> diff --git a/src/Components/Blazor/Build/src/targets/All.targets b/src/Components/Blazor/Build/src/targets/All.targets deleted file mode 100644 index 6c69e85a40113d3f0fd58291407c1b14d0144cf8..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/src/targets/All.targets +++ /dev/null @@ -1,49 +0,0 @@ -<Project> - - <!-- Require rebuild if the targets change --> - <PropertyGroup> - <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects> - </PropertyGroup> - - <PropertyGroup> - <BlazorToolsDir Condition="'$(BlazorToolsDir)' == ''">$(MSBuildThisFileDirectory)..\tools\</BlazorToolsDir> - <_BlazorTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp</_BlazorTasksTFM> - <_BlazorTasksTFM Condition=" '$(_BlazorTasksTFM)' == ''">netfx</_BlazorTasksTFM> - <BlazorTasksPath>$(BlazorToolsDir)$(_BlazorTasksTFM)\Microsoft.AspNetCore.Blazor.Build.Tasks.dll</BlazorTasksPath> - - <!-- The Blazor build code can only find your referenced assemblies if they are in the output directory --> - <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> - - <!-- By default, enable debugging for debug builds. --> - <BlazorEnableDebugging Condition="'$(Configuration)' == 'Debug' AND '$(BlazorEnableDebugging)' == ''">true</BlazorEnableDebugging> - </PropertyGroup> - - <Import Project="Blazor.MonoRuntime.targets" /> - <Import Project="Publish.targets" /> - <Import Project="StaticWebAssets.targets" /> - - <Target Name="GenerateBlazorMetadataFile" - BeforeTargets="GetCopyToOutputDirectoryItems"> - <PropertyGroup> - <BlazorMetadataFileName>$(AssemblyName).blazor.config</BlazorMetadataFileName> - <BlazorMetadataFilePath>$(TargetDir)$(BlazorMetadataFileName)</BlazorMetadataFilePath> - </PropertyGroup> - - <ItemGroup> - <_BlazorConfigContent Include="$(MSBuildProjectFullPath)" /> - <_BlazorConfigContent Include="$(TargetPath)" /> - <_BlazorConfigContent Include="debug:true" Condition="'$(BlazorEnableDebugging)'=='true'" /> - </ItemGroup> - - <WriteLinesToFile - File="$(BlazorMetadataFilePath)" - Lines="@(_BlazorConfigContent)" - Overwrite="true" - WriteOnlyWhenDifferent="True" /> - - <ItemGroup> - <ContentWithTargetPath Include="$(BlazorMetadataFilePath)" TargetPath="$(BlazorMetadataFileName)" CopyToOutputDirectory="PreserveNewest" /> - </ItemGroup> - </Target> - -</Project> diff --git a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.props b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.props deleted file mode 100644 index f49c1f8f2ff5e0d96b6476a1f16fa4cf23b02a84..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.props +++ /dev/null @@ -1,20 +0,0 @@ -<Project> - - <PropertyGroup> - <BlazorJsPath Condition="'$(BlazorJsPath)' == ''">$(MSBuildThisFileDirectory)..\tools\blazor\blazor.webassembly.js</BlazorJsPath> - </PropertyGroup> - - <PropertyGroup Label="Blazor build outputs"> - <MonoLinkerI18NAssemblies>none</MonoLinkerI18NAssemblies> <!-- See Mono linker docs - allows comma-separated values from: none,all,cjk,mideast,other,rare,west --> - <AdditionalMonoLinkerOptions>--disable-opt unreachablebodies --verbose --strip-security true --exclude-feature com -v false -c link -u link -b true</AdditionalMonoLinkerOptions> - <BaseBlazorDistPath>dist\</BaseBlazorDistPath> - <BaseBlazorPackageContentOutputPath>$(BaseBlazorDistPath)_content\</BaseBlazorPackageContentOutputPath> - <BaseBlazorRuntimeOutputPath>$(BaseBlazorDistPath)_framework\</BaseBlazorRuntimeOutputPath> - <BlazorRuntimeBinOutputPath>$(BaseBlazorRuntimeOutputPath)_bin\</BlazorRuntimeBinOutputPath> - <BlazorRuntimeWasmOutputPath>$(BaseBlazorRuntimeOutputPath)wasm\</BlazorRuntimeWasmOutputPath> - <BlazorWebRootName>wwwroot\</BlazorWebRootName> - <BlazorBootJsonName>blazor.boot.json</BlazorBootJsonName> - <_BlazorBuiltInBclLinkerDescriptor>$(MSBuildThisFileDirectory)BuiltInBclLinkerDescriptor.xml</_BlazorBuiltInBclLinkerDescriptor> - </PropertyGroup> - -</Project> diff --git a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets b/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets deleted file mode 100644 index 3c7d126561928d78dc3dac5dccea4d6320e3d047..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/src/targets/Blazor.MonoRuntime.targets +++ /dev/null @@ -1,336 +0,0 @@ -<Project> - <PropertyGroup> - <BlazorLinkOnBuild Condition="$(BlazorLinkOnBuild) == ''">true</BlazorLinkOnBuild> - </PropertyGroup> - - <PropertyGroup> - <!-- Stop-gap until we can migrate Blazor.Mono package to use better naming convention --> - <DotNetWebAssemblyBCLPath Condition="'$(DotNetWebAssemblyBCLPath)' == '' AND '$(MonoBaseClassLibraryPath)' != ''">$(MonoBaseClassLibraryPath)</DotNetWebAssemblyBCLPath> - <DotNetWebAssemblyBCLFacadesPath Condition="'$(DotNetWebAssemblyBCLFacadesPath)' == '' AND '$(MonoBaseClassLibraryFacadesPath)' != ''">$(MonoBaseClassLibraryFacadesPath)</DotNetWebAssemblyBCLFacadesPath> - <DotNetWebAssemblyRuntimePath Condition="'$(DotNetWebAssemblyRuntimePath)' == '' AND '$(MonoWasmRuntimePath)' != ''">$(MonoWasmRuntimePath)</DotNetWebAssemblyRuntimePath> - <DotNetWebAssemblyFrameworkPath Condition="'$(DotNetWebAssemblyFrameworkPath)' == '' AND '$(MonoWasmFrameworkPath)' != ''">$(MonoWasmFrameworkPath)</DotNetWebAssemblyFrameworkPath> - </PropertyGroup> - - <PropertyGroup Condition="'$(DotNetWebAssemblyArtifactsRoot)' != ''"> - <!-- Compute paths given a path to DotNet WASM artifacts. This is meant to make it easy to test WASM builds --> - <DotNetWebAssemblyBCLPath>$(DotNetWebAssemblyArtifactsRoot)\wasm-bcl\wasm\</DotNetWebAssemblyBCLPath> - <DotNetWebAssemblyBCLFacadesPath>$(DotNetWebAssemblyBCLPath)\Facades\</DotNetWebAssemblyBCLFacadesPath> - <DotNetWebAssemblyRuntimePath>$(DotNetWebAssemblyArtifactsRoot)\builds\debug\</DotNetWebAssemblyRuntimePath> - <DotNetWebAssemblyFrameworkPath>$(DotNetWebAssemblyArtifactsRoot)\framework\</DotNetWebAssemblyFrameworkPath> - </PropertyGroup> - - <Target - Name="_BlazorCopyFilesToOutputDirectory" - DependsOnTargets="PrepareBlazorOutputs" - AfterTargets="CopyFilesToOutputDirectory" - Condition="'$(OutputType.ToLowerInvariant())'=='exe'"> - - <!-- Copy the blazor output files --> - <Copy - SourceFiles="@(BlazorOutputWithTargetPath)" - DestinationFiles="@(BlazorOutputWithTargetPath->'$(TargetDir)%(TargetOutputPath)')" - SkipUnchangedFiles="$(SkipCopyUnchangedFiles)" - OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" - Retries="$(CopyRetryCount)" - RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" - UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)" - UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)" - Condition="'@(BlazorOutputWithTargetPath)' != '' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'"> - </Copy> - - <ItemGroup> - <FileWrites Include="@(BlazorOutputWithTargetPath->'$(TargetDir)%(TargetOutputPath)')" /> - </ItemGroup> - - <ItemGroup> - <_BlazorStatisticsOutput Include="@(BlazorOutputWithTargetPath->'%(TargetOutputPath)')" /> - </ItemGroup> - - <Message Importance="high" Text="$(TargetName) (Blazor output) -> $(TargetDir)dist" /> - </Target> - - <Target - Name="PrepareBlazorOutputs" - DependsOnTargets="_ResolveBlazorInputs;_ResolveBlazorOutputs;_GenerateBlazorBootJson"> - - <ItemGroup> - <MonoWasmFile Include="$(DotNetWebAssemblyRuntimePath)*" /> - <BlazorJSFile Include="$(BlazorJSPath)" /> - <BlazorJSFile Include="$(BlazorJSMapPath)" Condition="Exists('$(BlazorJSMapPath)')" /> - - <BlazorOutputWithTargetPath Include="@(MonoWasmFile)"> - <TargetOutputPath>$(BlazorRuntimeWasmOutputPath)%(FileName)%(Extension)</TargetOutputPath> - </BlazorOutputWithTargetPath> - <BlazorOutputWithTargetPath Include="@(BlazorJSFile)"> - <TargetOutputPath>$(BaseBlazorRuntimeOutputPath)%(FileName)%(Extension)</TargetOutputPath> - </BlazorOutputWithTargetPath> - </ItemGroup> - - <ItemGroup Label="Static content supplied by NuGet packages"> - <_BlazorPackageContentOutput Include="@(BlazorPackageContentFile)" Condition="%(SourcePackage) != ''"> - <TargetOutputPath>$(BaseBlazorPackageContentOutputPath)%(SourcePackage)\%(RecursiveDir)\%(Filename)%(Extension)</TargetOutputPath> - </_BlazorPackageContentOutput> - <BlazorOutputWithTargetPath Include="@(_BlazorPackageContentOutput)" /> - </ItemGroup> - </Target> - - <Target Name="_ResolveBlazorInputs" DependsOnTargets="ResolveReferences;ResolveRuntimePackAssets"> - <PropertyGroup> - <!-- /obj/<<configuration>>/<<targetframework>>/blazor --> - <BlazorIntermediateOutputPath>$(IntermediateOutputPath)blazor\</BlazorIntermediateOutputPath> - - <!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker.descriptor.xml --> - <GeneratedBlazorLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.descriptor.xml</GeneratedBlazorLinkerDescriptor> - - <_TypeGranularityLinkerDescriptor>$(BlazorIntermediateOutputPath)linker.typegranularityconfig.xml</_TypeGranularityLinkerDescriptor> - - <!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker/ --> - <BlazorIntermediateLinkerOutputPath>$(BlazorIntermediateOutputPath)linker/</BlazorIntermediateLinkerOutputPath> - - <!-- /obj/<<configuration>>/<<targetframework>>/blazor/blazor.boot.json --> - <BlazorBootJsonIntermediateOutputPath>$(BlazorIntermediateOutputPath)$(BlazorBootJsonName)</BlazorBootJsonIntermediateOutputPath> - - <_BlazorLinkerOutputCache>$(BlazorIntermediateOutputPath)linker.output</_BlazorLinkerOutputCache> - - <_BlazorApplicationAssembliesCacheFile>$(BlazorIntermediateOutputPath)unlinked.output</_BlazorApplicationAssembliesCacheFile> - </PropertyGroup> - - <ItemGroup> - <_WebAssemblyBCLFolder Include=" - $(DotNetWebAssemblyBCLPath); - $(DotNetWebAssemblyBCLFacadesPath); - $(DotNetWebAssemblyFrameworkPath)" /> - - <_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" /> - </ItemGroup> - - <!-- - Calculate the assemblies that act as inputs to calculate assembly closure. Based on _ComputeAssembliesToPostprocessOnPublish which is used as input to SDK's linker - https://github.com/dotnet/sdk/blob/d597e7b09d7657ba4e326d6734e14fcbf8473564/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets#L864-L873 - --> - <ItemGroup> - <!-- Assemblies from packages --> - <_BlazorManagedRuntimeAssemby Include="@(RuntimeCopyLocalItems)" /> - - <!-- Assemblies from other references --> - <_BlazorUserRuntimeAssembly Include="@(ReferencePath->WithMetadataValue('CopyLocal', 'true'))" /> - <_BlazorUserRuntimeAssembly Include="@(ReferenceDependencyPaths->WithMetadataValue('CopyLocal', 'true'))" /> - - <_BlazorManagedRuntimeAssemby Include="@(_BlazorUserRuntimeAssembly)" /> - <_BlazorManagedRuntimeAssemby Include="@(IntermediateAssembly)" /> - </ItemGroup> - - <MakeDir Directories="$(BlazorIntermediateOutputPath)" /> - </Target> - - <Target Name="_ResolveBlazorOutputs" DependsOnTargets="_ResolveBlazorOutputsWhenLinked;_ResolveBlazorOutputsWhenNotLinked"> - <Error - Message="Unrecongnized value for BlazorLinkOnBuild: '$(BlazorLinkOnBuild)'. Valid values are 'true' or 'false'." - Condition="'$(BlazorLinkOnBuild)' != 'true' AND '$(BlazorLinkOnBuild)' != 'false'" /> - - <ItemGroup> - <!-- - ReferenceCopyLocalPaths includes all files that are part of the build out with CopyLocalLockFileAssemblies on. - Remove assemblies that are inputs to calculating the assembly closure. Instead use the resolved outputs, since it is the minimal set. - --> - <_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" /> - <_BlazorCopyLocalPaths Remove="@(_BlazorManagedRuntimeAssemby)" /> - - <BlazorOutputWithTargetPath Include="@(_BlazorCopyLocalPaths)"> - <BlazorRuntimeFile>true</BlazorRuntimeFile> - <TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath> - <RelativeOutputPath>%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</RelativeOutputPath> - </BlazorOutputWithTargetPath> - - <BlazorOutputWithTargetPath Include="@(_BlazorResolvedAssembly)"> - <BlazorRuntimeFile>true</BlazorRuntimeFile> - <TargetOutputPath>$(BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath> - <RelativeOutputPath>%(FileName)%(Extension)</RelativeOutputPath> - </BlazorOutputWithTargetPath> - </ItemGroup> - </Target> - - <!-- - Linker enabled part of the pipeline: - - * If there are no descriptors defined, generate a new linker descriptor. - * Invoke the linker and write linked files to a well-known directory. - * Collect the outputs of the linker. - --> - - <Target - Name="_ResolveBlazorOutputsWhenLinked" - Condition="'$(BlazorLinkOnBuild)' == 'true'" - DependsOnTargets="_PrepareBlazorLinkerInputs;_GenerateBlazorLinkerDescriptor;_GenerateTypeGranularLinkerDescriptor;_LinkBlazorApplication"> - - <!-- _BlazorLinkerOutputCache records files linked during the last incremental build of the target. Read the contents and assign linked files to be copied to the output. --> - <ReadLinesFromFile File="$(_BlazorLinkerOutputCache)"> - <Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/> - </ReadLinesFromFile> - </Target> - - <Target Name="_PrepareBlazorLinkerInputs"> - <ItemGroup> - <_BlazorRuntimeCopyLocalItems Include="@(RuntimeCopyLocalItems)" /> - - <!-- - Any assembly from a package reference that starts with System. file name is allowed to be linked. - Assemblies from Microsoft.AspNetCore and Microsoft.Extensions, are also linked but with TypeGranularity. - --> - <_BlazorRuntimeCopyLocalItems IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" /> - <_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" /> - <_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" /> - - <_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" /> - <_BlazorAssemblyToLink Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' == 'true'" /> - - <_BlazorLinkerRoot Include="@(IntermediateAssembly)" /> - <_BlazorLinkerRoot Include="@(_BlazorUserRuntimeAssembly)" /> - <_BlazorLinkerRoot Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' != 'true'" /> - </ItemGroup> - - </Target> - - <UsingTask TaskName="BlazorCreateRootDescriptorFile" AssemblyFile="$(BlazorTasksPath)" /> - <Target Name="_GenerateBlazorLinkerDescriptor" - Inputs="@(IntermediateAssembly)" - Outputs="$(GeneratedBlazorLinkerDescriptor)" - Condition="'@(BlazorLinkerDescriptor)' == ''"> - - <!-- Generate linker descriptors if the project doesn't explicitly provide one. --> - - <BlazorCreateRootDescriptorFile - AssemblyNames="@(IntermediateAssembly->'%(Filename)')" - RootDescriptorFilePath="$(GeneratedBlazorLinkerDescriptor)" /> - - <ItemGroup> - <FileWrites Include="$(GeneratedBlazorLinkerDescriptor)" /> - <BlazorLinkerDescriptor Include="$(GeneratedBlazorLinkerDescriptor)" /> - <BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" /> - </ItemGroup> - </Target> - - <UsingTask TaskName="GenerateTypeGranularityLinkingConfig" AssemblyFile="$(BlazorTasksPath)" /> - <Target Name="_GenerateTypeGranularLinkerDescriptor" - Inputs="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))" - Outputs="$(_TypeGranularityLinkerDescriptor)"> - - <GenerateTypeGranularityLinkingConfig - Assemblies="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))" - OutputPath="$(_TypeGranularityLinkerDescriptor)" /> - - <ItemGroup> - <BlazorLinkerDescriptor Include="$(_TypeGranularityLinkerDescriptor)" /> - <FileWrites Include="$(_TypeGranularityLinkerDescriptor)" /> - </ItemGroup> - </Target> - - <UsingTask TaskName="BlazorILLink" AssemblyFile="$(BlazorTasksPath)" /> - <Target - Name="_LinkBlazorApplication" - Inputs="$(ProjectAssetsFile); - @(_BlazorManagedRuntimeAssemby); - @(BlazorLinkerDescriptor); - $(MSBuildAllProjects)" - Outputs="$(_BlazorLinkerOutputCache)"> - - <PropertyGroup> - <_BlazorLinkerAdditionalOptions>-l $(MonoLinkerI18NAssemblies) $(AdditionalMonoLinkerOptions)</_BlazorLinkerAdditionalOptions> - </PropertyGroup> - - <ItemGroup> - <_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.dll" /> - <_OldLinkedFile Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" /> - </ItemGroup> - - <Delete Files="@(_OldLinkedFile)" /> - - <!-- - When running from Desktop MSBuild, DOTNET_HOST_PATH is not set. - In this case, explicitly specify the path to the dotnet host. - --> - <PropertyGroup Condition=" '$(DOTNET_HOST_PATH)' == '' "> - <_DotNetHostDirectory>$(NetCoreRoot)</_DotNetHostDirectory> - <_DotNetHostFileName>dotnet</_DotNetHostFileName> - <_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe</_DotNetHostFileName> - </PropertyGroup> - - <BlazorILLink - ILLinkPath="$(MonoLinkerPath)" - AssemblyPaths="@(_BlazorAssemblyToLink)" - RootAssemblyNames="@(_BlazorLinkerRoot)" - RootDescriptorFiles="@(BlazorLinkerDescriptor)" - OutputDirectory="$(BlazorIntermediateLinkerOutputPath)" - ExtraArgs="$(_BlazorLinkerAdditionalOptions)" - ToolExe="$(_DotNetHostFileName)" - ToolPath="$(_DotNetHostDirectory)" /> - - <ItemGroup> - <_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.dll" /> - <_LinkerResult Include="$(BlazorIntermediateLinkerOutputPath)*.pdb" Condition="'$(BlazorEnableDebugging)' == 'true'" /> - </ItemGroup> - - <WriteLinesToFile File="$(_BlazorLinkerOutputCache)" Lines="@(_LinkerResult)" Overwrite="true" /> - </Target> - - <UsingTask TaskName="ResolveBlazorRuntimeDependencies" AssemblyFile="$(BlazorTasksPath)" /> - <Target - Name="_ResolveBlazorOutputsWhenNotLinked" - DependsOnTargets="_ResolveBlazorRuntimeDependencies" - Condition="'$(BlazorLinkOnBuild)' != 'true'"> - - <ReadLinesFromFile File="$(_BlazorApplicationAssembliesCacheFile)" Condition="'@(_BlazorResolvedAssembly->Count())' == '0'"> - <Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/> - </ReadLinesFromFile> - </Target> - - <Target - Name="_ResolveBlazorRuntimeDependencies" - Inputs="$(ProjectAssetsFile); - @(IntermediateAssembly); - @(_BlazorManagedRuntimeAssemby)" - Outputs="$(_BlazorApplicationAssembliesCacheFile)"> - - <!-- - At this point we have decided not to run the linker and instead to just copy the assemblies - from the BCL referenced by the app the nuget package into the _framework/_bin folder. - The only thing we need to do here is collect the list of items that will go into _framework/_bin. - --> - <ResolveBlazorRuntimeDependencies - EntryPoint="@(IntermediateAssembly)" - ApplicationDependencies="@(_BlazorManagedRuntimeAssemby)" - WebAssemblyBCLAssemblies="@(_WebAssemblyBCLAssembly)"> - - <Output TaskParameter="Dependencies" ItemName="_BlazorResolvedAssembly" /> - </ResolveBlazorRuntimeDependencies> - - <WriteLinesToFile File="$(_BlazorApplicationAssembliesCacheFile)" Lines="@(_BlazorResolvedRuntimeDependencies)" Overwrite="true" /> - - <ItemGroup> - <FileWrites Include="$(_BlazorApplicationAssembliesCacheFile)" /> - </ItemGroup> - </Target> - - <UsingTask TaskName="GenerateBlazorBootJson" AssemblyFile="$(BlazorTasksPath)" /> - - <Target - Name="_GenerateBlazorBootJson" - Inputs="@(BlazorOutputWithTargetPath)" - Outputs="$(BlazorBootJsonIntermediateOutputPath)"> - <ItemGroup> - <_BlazorRuntimeFile Include="@(BlazorOutputWithTargetPath->WithMetadataValue('BlazorRuntimeFile', 'true'))" /> - </ItemGroup> - - <GenerateBlazorBootJson - AssemblyPath="@(IntermediateAssembly)" - References="@(_BlazorRuntimeFile)" - LinkerEnabled="$(BlazorLinkOnBuild)" - OutputPath="$(BlazorBootJsonIntermediateOutputPath)" /> - - <ItemGroup> - <BlazorOutputWithTargetPath Include="$(BlazorBootJsonIntermediateOutputPath)" TargetOutputPath="$(BaseBlazorRuntimeOutputPath)$(BlazorBootJsonName)" /> - <FileWrites Include="$(BlazorBootJsonIntermediateOutputPath)" /> - </ItemGroup> - </Target> - -</Project> diff --git a/src/Components/Blazor/Build/src/targets/Publish.targets b/src/Components/Blazor/Build/src/targets/Publish.targets deleted file mode 100644 index 7cb7e0ad231df448f67a180d595c303aac7c22d8..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/src/targets/Publish.targets +++ /dev/null @@ -1,70 +0,0 @@ -<Project> - <PropertyGroup> - <BlazorLinkOnBuild Condition="'$(BlazorLinkOnBuild)'==''">true</BlazorLinkOnBuild> - <BlazorPublishDistDir>$(AssemblyName)\dist\</BlazorPublishDistDir> - - <!-- Disable unwanted parts of the default publish process --> - <CopyBuildOutputToPublishDirectory>false</CopyBuildOutputToPublishDirectory> - <CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory> - <PreserveCompilationContext>false</PreserveCompilationContext> - <RazorCompileOnPublish>false</RazorCompileOnPublish> - <GenerateDependencyFile>false</GenerateDependencyFile> - <IsWebConfigTransformDisabled>true</IsWebConfigTransformDisabled> - </PropertyGroup> - - <Target Name="BlazorGetCopyToPublishDirectoryItems" - BeforeTargets="GetCopyToPublishDirectoryItems" - DependsOnTargets="PrepareBlazorOutputs" - Condition="'$(OutputType.ToLowerInvariant())'=='exe'"> - <ItemGroup> - <!-- Don't want to publish the assemblies from the regular 'bin' dir. Instead we publish ones from 'dist'. --> - <ResolvedAssembliesToPublish Remove="@(ResolvedAssembliesToPublish)" /> - - <!-- Move wwwroot files to output root --> - <ContentWithTargetPath Update="@(ContentWithTargetPath)" Condition="$([System.String]::new(%(TargetPath)).StartsWith('wwwroot\')) OR $([System.String]::new(%(TargetPath)).StartsWith('wwwroot/'))"> - <TargetPath>$(BlazorPublishDistDir)$([System.String]::new(%(TargetPath)).Substring(8))</TargetPath> - </ContentWithTargetPath> - - <!-- Publish all the 'dist' files --> - <_BlazorGCTPDI Include="%(BlazorOutputWithTargetPath.Identity)"> - <TargetPath>$(AssemblyName)\%(TargetOutputPath)</TargetPath> - </_BlazorGCTPDI> - - <ContentWithTargetPath Include="@(_BlazorGCTPDI)"> - <TargetPath>%(TargetPath)</TargetPath> - <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> - </ContentWithTargetPath> - </ItemGroup> - - <!-- Replace the .blazor.config contents with what we need to serve in production --> - <PropertyGroup> - <_BlazorConfigPath>$(OutDir)$(AssemblyName).blazor.config</_BlazorConfigPath> - </PropertyGroup> - - <ItemGroup> - <_BlazorPublishConfigContent Include="." /> - <_BlazorPublishConfigContent Include="$(AssemblyName)/" /> - </ItemGroup> - - <WriteLinesToFile - File="$(_BlazorConfigPath)" - Lines="@(_BlazorPublishConfigContent)" - Overwrite="true" - WriteOnlyWhenDifferent="true" /> - </Target> - - <!-- The following target runs only for standalone publishing --> - <Target Name="BlazorCompleteStandalonePublish" AfterTargets="CopyFilesToPublishDirectory"> - <!-- Add a suitable web.config file if there isn't one already --> - <ItemGroup> - <_StandaloneWebConfigContent Include="$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)Standalone.Web.config'))"/> - </ItemGroup> - <WriteLinesToFile - Condition="!Exists('$(PublishDir)web.config')" - File="$(PublishDir)web.config" - Lines="@(_StandaloneWebConfigContent->Replace('[ServeSubdirectory]','$(BlazorPublishDistDir)'))" /> - - <!-- Remove the .blazor.config file, since it's irrelevant for standalone publishing --> - <Delete Files="$(PublishDir)$(AssemblyName).blazor.config" /> - </Target> -</Project> diff --git a/src/Components/Blazor/Build/src/targets/StaticWebAssets.targets b/src/Components/Blazor/Build/src/targets/StaticWebAssets.targets deleted file mode 100644 index d547f500b13ffbe796890547fe283571dd764b27..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/src/targets/StaticWebAssets.targets +++ /dev/null @@ -1,37 +0,0 @@ -<Project> - - <PropertyGroup> - <ResolveStaticWebAssetsInputsDependsOn> - $(ResolveStaticWebAssetsInputsDependsOn); - _RemoveBlazorCurrentProjectAssetsFromStaticWebAssets; - </ResolveStaticWebAssetsInputsDependsOn> - - <GetCurrentProjectStaticWebAssetsDependsOn> - $(GetCurrentProjectStaticWebAssetsDependsOn); - _RemoveBlazorCurrentProjectAssetsFromStaticWebAssets; - </GetCurrentProjectStaticWebAssetsDependsOn> - </PropertyGroup> - - - <Target Name="_RemoveBlazorCurrentProjectAssetsFromStaticWebAssets"> - <ItemGroup> - <StaticWebAsset Remove="@(StaticWebAsset)" Condition="'%(SourceType)' == ''" /> - </ItemGroup> - </Target> - - <Target Name="BlazorStaticWebAssetsComputeFilesToPublish" - AfterTargets="_StaticWebAssetsComputeFilesToPublish"> - - <ItemGroup> - <!-- We need to update the external static web assets to follow the blazor publish output convention that puts them inside $(TargetName)/dist instead of wwwroot --> - <_StandaloneExternalPublishStaticWebAsset Include="@(_ExternalPublishStaticWebAsset)" Condition="'%(RelativePath)' != ''"> - <RelativePath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$([MSBuild]::NormalizePath('$([System.Text.RegularExpressions.Regex]::Replace('%(RelativePath)','^wwwroot\\?\/?(.*)','$(BlazorPublishDistDir)$1'))'))'))</RelativePath> - </_StandaloneExternalPublishStaticWebAsset> - - <!-- Update doesn't work inside targets so we need to remove the items and re-add them. See https://github.com/microsoft/msbuild/issues/2835 for details --> - <ResolvedFileToPublish Remove="@(_StandaloneExternalPublishStaticWebAsset)" /> - <ResolvedFileToPublish Include="@(_StandaloneExternalPublishStaticWebAsset)" /> - - </ItemGroup> - </Target> -</Project> diff --git a/src/Components/Blazor/Build/test/BindRazorIntegrationTest.cs b/src/Components/Blazor/Build/test/BindRazorIntegrationTest.cs deleted file mode 100644 index 1fafb9e81dbd725c00ea7659627207aba363e069..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/BindRazorIntegrationTest.cs +++ /dev/null @@ -1,534 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Test.Helpers; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.AspNetCore.Blazor.Build.Test -{ - public class BindRazorIntegrationTest : RazorIntegrationTestBase - { - public BindRazorIntegrationTest(ITestOutputHelper output) - : base(output) - { - } - - internal override bool UseTwoPhaseCompilation => true; - - [Fact] - public void Render_BindToComponent_SpecifiesValue_WithMatchingProperties() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using System; -using Microsoft.AspNetCore.Components; - -namespace Test -{ - public class MyComponent : ComponentBase - { - [Parameter] - public int Value { get; set; } - - [Parameter] - public Action<int> ValueChanged { get; set; } - } -}")); - - var component = CompileToComponent(@" -<MyComponent @bind-Value=""ParentValue"" /> -@code { - public int ParentValue { get; set; } = 42; -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0), - frame => AssertFrame.Attribute(frame, "Value", 42, 1), - frame => AssertFrame.Attribute(frame, "ValueChanged", typeof(Action<int>), 2)); - } - - [Fact] - public void Render_BindToComponent_SpecifiesValue_WithoutMatchingProperties() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; - -namespace Test -{ - public class MyComponent : ComponentBase, IComponent - { - Task IComponent.SetParametersAsync(ParameterView parameters) - { - return Task.CompletedTask; - } - } -}")); - - var component = CompileToComponent(@" -<MyComponent @bind-Value=""ParentValue"" /> -@code { - public int ParentValue { get; set; } = 42; -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0), - frame => AssertFrame.Attribute(frame, "Value", 42, 1), - frame => AssertFrame.Attribute(frame, "ValueChanged", typeof(EventCallback<int>), 2)); - } - - [Fact] - public void Render_BindToComponent_SpecifiesValueAndChangeEvent_WithMatchingProperties() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using System; -using Microsoft.AspNetCore.Components; - -namespace Test -{ - public class MyComponent : ComponentBase - { - [Parameter] - public int Value { get; set; } - - [Parameter] - public Action<int> OnChanged { get; set; } - } -}")); - - var component = CompileToComponent(@" -<MyComponent @bind-Value=""ParentValue"" @bind-Value:event=""OnChanged"" /> -@code { - public int ParentValue { get; set; } = 42; -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0), - frame => AssertFrame.Attribute(frame, "Value", 42, 1), - frame => AssertFrame.Attribute(frame, "OnChanged", typeof(Action<int>), 2)); - } - - [Fact] - public void Render_BindToComponent_SpecifiesValueAndChangeEvent_WithoutMatchingProperties() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; - -namespace Test -{ - public class MyComponent : ComponentBase, IComponent - { - Task IComponent.SetParametersAsync(ParameterView parameters) - { - return Task.CompletedTask; - } - } -}")); - - var component = CompileToComponent(@" -<MyComponent @bind-Value=""ParentValue"" @bind-Value:event=""OnChanged"" /> -@code { - public int ParentValue { get; set; } = 42; -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0), - frame => AssertFrame.Attribute(frame, "Value", 42, 1), - frame => AssertFrame.Attribute(frame, "OnChanged", typeof(EventCallback<int>), 2)); - } - - [Fact] - public void Render_BindToElement_WritesAttributes() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using System; -using Microsoft.AspNetCore.Components; - -namespace Test -{ - [BindElement(""div"", null, ""myvalue"", ""myevent"")] - public static class BindAttributes - { - } -}")); - - var component = CompileToComponent(@" -<div @bind=""@ParentValue"" /> -@code { - public string ParentValue { get; set; } = ""hi""; -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "div", 3, 0), - frame => AssertFrame.Attribute(frame, "myvalue", "hi", 1), - frame => AssertFrame.Attribute(frame, "myevent", typeof(EventCallback), 2)); - } - - [Fact] - public void Render_BindToElementWithSuffix_WritesAttributes() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using System; -using Microsoft.AspNetCore.Components; - -namespace Test -{ - [BindElement(""div"", ""value"", ""myvalue"", ""myevent"")] - public static class BindAttributes - { - } -}")); - - var component = CompileToComponent(@" -<div @bind-value=""@ParentValue"" /> -@code { - public string ParentValue { get; set; } = ""hi""; -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "div", 3, 0), - frame => AssertFrame.Attribute(frame, "myvalue", "hi", 1), - frame => AssertFrame.Attribute(frame, "myevent", typeof(EventCallback), 2)); - } - - [Fact] - public void Render_BindDuplicates_ReportsDiagnostic() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using System; -using Microsoft.AspNetCore.Components; - -namespace Test -{ - [BindElement(""div"", ""value"", ""myvalue2"", ""myevent2"")] - [BindElement(""div"", ""value"", ""myvalue"", ""myevent"")] - public static class BindAttributes - { - } -}")); - - // Act - var result = CompileToCSharp(@" -<div @bind-value=""@ParentValue"" /> -@code { - public string ParentValue { get; set; } = ""hi""; -}"); - - // Assert - var diagnostic = Assert.Single(result.Diagnostics); - Assert.Equal("RZ9989", diagnostic.Id); - Assert.Equal( - "The attribute '@bind-value' was matched by multiple bind attributes. Duplicates:" + Environment.NewLine + - "Test.BindAttributes" + Environment.NewLine + - "Test.BindAttributes", - diagnostic.GetMessage()); - } - - [Fact] - public void Render_BuiltIn_BindToInputWithoutType_WritesAttributes() - { - // Arrange - var component = CompileToComponent(@" -@using Microsoft.AspNetCore.Components.Web -<input @bind=""@ParentValue"" /> -@code { - public int ParentValue { get; set; } = 42; -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "input", 3, 0), - frame => AssertFrame.Attribute(frame, "value", "42", 1), - frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 2)); - } - - [Fact] - public void Render_BuiltIn_BindToInputText_WithFormat_WritesAttributes() - { - // Arrange - var component = CompileToComponent(@" -@using Microsoft.AspNetCore.Components.Web -<input type=""text"" @bind=""@CurrentDate"" @bind:format=""MM/dd/yyyy""/> -@code { - public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "input", 4, 0), - frame => AssertFrame.Attribute(frame, "type", "text", 1), - frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd/yyyy"), 2), - frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3)); - } - - [Fact] - public void Render_BuiltIn_BindToInputText_WithFormatFromProperty_WritesAttributes() - { - // Arrange - var component = CompileToComponent(@" -@using Microsoft.AspNetCore.Components.Web -<input type=""text"" @bind=""@CurrentDate"" @bind:format=""@Format""/> -@code { - public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); - - public string Format { get; set; } = ""MM/dd/yyyy""; -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "input", 4, 0), - frame => AssertFrame.Attribute(frame, "type", "text", 1), - frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd/yyyy"), 2), - frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3)); - } - - [Fact] - public void Render_BuiltIn_BindToInputText_WritesAttributes() - { - // Arrange - var component = CompileToComponent(@" -@using Microsoft.AspNetCore.Components.Web -<input type=""text"" @bind=""@ParentValue"" /> -@code { - public int ParentValue { get; set; } = 42; -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "input", 4, 0), - frame => AssertFrame.Attribute(frame, "type", "text", 1), - frame => AssertFrame.Attribute(frame, "value", "42", 2), - frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3)); - } - - [Fact] - public void Render_BuiltIn_BindToInputCheckbox_WritesAttributes() - { - // Arrange - var component = CompileToComponent(@" -@using Microsoft.AspNetCore.Components.Web -<input type=""checkbox"" @bind=""@Enabled"" /> -@code { - public bool Enabled { get; set; } -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "input", 3, 0), - frame => AssertFrame.Attribute(frame, "type", "checkbox", 1), - frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3)); - } - - [Fact] - public void Render_BindToElementFallback_WritesAttributes() - { - // Arrange - var component = CompileToComponent(@" -<input type=""text"" @bind-value=""@ParentValue"" @bind-value:event=""onchange"" /> -@code { - public int ParentValue { get; set; } = 42; -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "input", 4, 0), - frame => AssertFrame.Attribute(frame, "type", "text", 1), - frame => AssertFrame.Attribute(frame, "value", "42", 2), - frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3)); - } - - [Fact] - public void Render_BindToElementFallback_WithFormat_WritesAttributes() - { - // Arrange - var component = CompileToComponent(@" -<input type=""text"" @bind-value=""@CurrentDate"" @bind-value:event=""onchange"" @bind-value:format=""MM/dd"" /> -@code { - public DateTime CurrentDate { get; set; } = new DateTime(2018, 1, 1); -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "input", 4, 0), - frame => AssertFrame.Attribute(frame, "type", "text", 1), - frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 1, 1).ToString("MM/dd"), 2), - frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 3)); - } - - [Fact] // Additional coverage of OrphanTagHelperLoweringPass - public void Render_BindToElementFallback_SpecifiesValueAndChangeEvent_WithCSharpAttribute() - { - // Arrange - var component = CompileToComponent(@" -<input type=""@(""text"")"" @bind-value=""@ParentValue"" @bind-value:event=""onchange"" visible /> -@code { - public int ParentValue { get; set; } = 42; -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "input", 5, 0), - frame => AssertFrame.Attribute(frame, "type", "text", 1), - frame => AssertFrame.Attribute(frame, "visible", 2), - frame => AssertFrame.Attribute(frame, "value", "42", 3), - frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 4)); - } - - [Fact] // See https://github.com/aspnet/Blazor/issues/703 - public void Workaround_703() - { - // Arrange - var component = CompileToComponent(@" -<input @bind-value=""@ParentValue"" @bind-value:event=""onchange"" type=""text"" visible /> -@code { - public int ParentValue { get; set; } = 42; -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - // - // The workaround for 703 is that the value attribute MUST be after the type - // attribute. - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "input", 5, 0), - frame => AssertFrame.Attribute(frame, "type", "text", 1), - frame => AssertFrame.Attribute(frame, "visible", 2), - frame => AssertFrame.Attribute(frame, "value", "42", 3), - frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 4)); - } - - [Fact] // Additional coverage of OrphanTagHelperLoweringPass - public void Render_BindToElementFallback_SpecifiesValueAndChangeEvent_BodyContent() - { - // Arrange - var component = CompileToComponent(@" -<div @bind-value=""@ParentValue"" @bind-value:event=""onchange""> - <span>@(42.ToString())</span> -</div> -@code { - public int ParentValue { get; set; } = 42; -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "div", 7, 0), - frame => AssertFrame.Attribute(frame, "value", "42", 1), - frame => AssertFrame.Attribute(frame, "onchange", typeof(EventCallback), 2), - frame => AssertFrame.MarkupWhitespace(frame, 3), - frame => AssertFrame.Element(frame, "span", 2, 4), - frame => AssertFrame.Text(frame, "42", 5), - frame => AssertFrame.MarkupWhitespace(frame, 6)); - } - - [Fact] - public void Render_BindFallback_InvalidSyntax_TooManyParts() - { - // Arrange & Act - var generated = CompileToCSharp(@" -<input type=""text"" @bind-first-second-third=""Text"" /> -@code { - public string Text { get; set; } = ""text""; -}"); - - // Assert - var diagnostic = Assert.Single(generated.Diagnostics); - Assert.Equal("RZ9991", diagnostic.Id); - } - - [Fact] - public void Render_BindFallback_InvalidSyntax_TrailingDash() - { - // Arrange & Act - var generated = CompileToCSharp(@" -<input type=""text"" @bind-first-=""Text"" /> -@code { - public string Text { get; set; } = ""text""; -}"); - - // Assert - var diagnostic = Assert.Single(generated.Diagnostics); - Assert.Equal("RZ9991", diagnostic.Id); - } - } -} diff --git a/src/Components/Blazor/Build/test/BootJsonWriterTest.cs b/src/Components/Blazor/Build/test/BootJsonWriterTest.cs deleted file mode 100644 index 1e2d89b573bba6b7c68959aaebec563104246524..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/BootJsonWriterTest.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.IO; -using System.Text.Json; -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.AspNetCore.Blazor.Build -{ - public class BootJsonWriterTest - { - [Fact] - public async Task ProducesJsonReferencingAssemblyAndDependencies() - { - // Arrange/Act - var assemblyReferences = new string[] { "MyApp.EntryPoint.dll", "System.Abc.dll", "MyApp.ClassLib.dll", }; - using var stream = new MemoryStream(); - - // Act - GenerateBlazorBootJson.WriteBootJson( - stream, - "MyApp.Entrypoint.dll", - assemblyReferences, - linkerEnabled: true); - - // Assert - stream.Position = 0; - using var parsedContent = await JsonDocument.ParseAsync(stream); - var rootElement = parsedContent.RootElement; - Assert.Equal("MyApp.Entrypoint.dll", rootElement.GetProperty("entryAssembly").GetString()); - var assembliesElement = rootElement.GetProperty("assemblies"); - Assert.Equal(assemblyReferences.Length, assembliesElement.GetArrayLength()); - for (var i = 0; i < assemblyReferences.Length; i++) - { - Assert.Equal(assemblyReferences[i], assembliesElement[i].GetString()); - } - Assert.True(rootElement.GetProperty("linkerEnabled").GetBoolean()); - } - } -} diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIncrementalismTest.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIncrementalismTest.cs deleted file mode 100644 index 73d864502989f87f6c789045613638dfc103135f..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIncrementalismTest.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.AspNetCore.Blazor.Build -{ - public class BuildIncrementalismTest - { - [Fact] - public async Task Build_WithLinker_IsIncremental() - { - // Arrange - using var project = ProjectDirectory.Create("standalone"); - var result = await MSBuildProcessManager.DotnetMSBuild(project); - - Assert.BuildPassed(result); - - var buildOutputDirectory = project.BuildOutputDirectory; - - // Act - var thumbPrint = FileThumbPrint.CreateFolderThumbprint(project, project.BuildOutputDirectory); - - // Assert - for (var i = 0; i < 3; i++) - { - result = await MSBuildProcessManager.DotnetMSBuild(project); - Assert.BuildPassed(result); - - var newThumbPrint = FileThumbPrint.CreateFolderThumbprint(project, project.BuildOutputDirectory); - Assert.Equal(thumbPrint.Count, newThumbPrint.Count); - for (var j = 0; j < thumbPrint.Count; j++) - { - Assert.Equal(thumbPrint[j], newThumbPrint[j]); - } - } - } - } -} diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs deleted file mode 100644 index f3148b1a0b981576d46ceb97d9115d4be4681eba..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.IO; -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.AspNetCore.Blazor.Build -{ - public class BuildIntegrationTest - { - [Fact] - public async Task Build_WithDefaultSettings_Works() - { - // Arrange - using var project = ProjectDirectory.Create("standalone"); - var result = await MSBuildProcessManager.DotnetMSBuild(project); - - Assert.BuildPassed(result); - - var buildOutputDirectory = project.BuildOutputDirectory; - - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.boot.json"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.webassembly.js"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.wasm"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.js"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. - } - - [Fact] - public async Task Build_Hosted_Works() - { - // Arrange - using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", }); - project.TargetFramework = "netcoreapp3.1"; - var result = await MSBuildProcessManager.DotnetMSBuild(project); - - Assert.BuildPassed(result); - - var buildOutputDirectory = project.BuildOutputDirectory; - var blazorConfig = Path.Combine(buildOutputDirectory, "standalone.blazor.config"); - Assert.FileExists(result, blazorConfig); - - var path = Path.GetFullPath(Path.Combine(project.SolutionPath, "standalone", "bin", project.Configuration, "netstandard2.1", "standalone.dll")); - Assert.FileContains(result, blazorConfig, path); - Assert.FileDoesNotExist(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll"); - } - - [Fact] - public async Task Build_WithLinkOnBuildDisabled_Works() - { - // Arrange - using var project = ProjectDirectory.Create("standalone"); - project.AddProjectFileContent( -@"<PropertyGroup> - <BlazorLinkOnBuild>false</BlazorLinkOnBuild> -</PropertyGroup>"); - - var result = await MSBuildProcessManager.DotnetMSBuild(project); - - Assert.BuildPassed(result); - - var buildOutputDirectory = project.BuildOutputDirectory; - - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.boot.json"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "blazor.webassembly.js"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.wasm"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "wasm", "dotnet.js"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. - } - - [Fact] - public async Task Build_SatelliteAssembliesAreCopiedToBuildOutput() - { - // Arrange - using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" }); - project.AddProjectFileContent( -@" -<PropertyGroup> - <DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants> -</PropertyGroup> -<ItemGroup> - <ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" /> -</ItemGroup>"); - - var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore"); - - Assert.BuildPassed(result); - - var buildOutputDirectory = project.BuildOutputDirectory; - - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output. - - var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json"); - Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\""); - Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); - } - - [Fact] - public async Task Build_WithBlazorLinkOnBuildFalse_SatelliteAssembliesAreCopiedToBuildOutput() - { - // Arrange - using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" }); - project.AddProjectFileContent( -@" -<PropertyGroup> - <BlazorLinkOnBuild>false</BlazorLinkOnBuild> - <DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants> -</PropertyGroup> -<ItemGroup> - <ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" /> -</ItemGroup>"); - - var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore"); - - Assert.BuildPassed(result); - - var buildOutputDirectory = project.BuildOutputDirectory; - - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "standalone.dll"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll"); - Assert.FileExists(result, buildOutputDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output. - - var bootJsonPath = Path.Combine(buildOutputDirectory, "dist", "_framework", "blazor.boot.json"); - Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\""); - Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); - } - } -} diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs b/src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs deleted file mode 100644 index 3556f119f4050cc46b0affab20b115b433b47dbc..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.IO; -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.AspNetCore.Blazor.Build -{ - public class PublishIntegrationTest - { - [Fact] - public async Task Publish_WithDefaultSettings_Works() - { - // Arrange - using var project = ProjectDirectory.Create("standalone", additionalProjects: new [] { "razorclasslibrary" }); - var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish"); - - Assert.BuildPassed(result); - - var publishDirectory = project.PublishOutputDirectory; - var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath)); - - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. - - // Verify referenced static web assets - Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css"); - - // Verify static assets are in the publish directory - Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); - - // Verify web.config - Assert.FileExists(result, publishDirectory, "web.config"); - } - - [Fact] - public async Task Publish_WithNoBuild_Works() - { - // Arrange - using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); - var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build"); - - Assert.BuildPassed(result); - - result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:NoBuild=true"); - - Assert.BuildPassed(result); - - var publishDirectory = project.PublishOutputDirectory; - var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath)); - - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. - - // Verify static assets are in the publish directory - Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); - - // Verify static web assets from referenced projects are copied. - // Uncomment once https://github.com/aspnet/AspNetCore/issues/17426 is resolved. - // Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); - // Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css"); - - // Verify static assets are in the publish directory - Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); - - // Verify web.config - Assert.FileExists(result, publishDirectory, "web.config"); - } - - [Fact] - public async Task Publish_WithLinkOnBuildDisabled_Works() - { - // Arrange - using var project = ProjectDirectory.Create("standalone", additionalProjects: new [] { "razorclasslibrary" }); - project.AddProjectFileContent( -@"<PropertyGroup> - <BlazorLinkOnBuild>false</BlazorLinkOnBuild> -</PropertyGroup>"); - - var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish"); - - Assert.BuildPassed(result); - - var publishDirectory = project.PublishOutputDirectory; - var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath)); - - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. - - // Verify static assets are in the publish directory - Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); - - // Verify referenced static web assets - Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_content", "RazorClassLibrary", "styles.css"); - - // Verify web.config - Assert.FileExists(result, publishDirectory, "web.config"); - } - - [Fact] - public async Task Publish_SatelliteAssemblies_AreCopiedToBuildOutput() - { - // Arrange - using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" }); - project.AddProjectFileContent( -@" -<PropertyGroup> - <DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants> -</PropertyGroup> -<ItemGroup> - <ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" /> -</ItemGroup>"); - - var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", args: "/restore"); - - Assert.BuildPassed(result); - - var publishDirectory = project.PublishOutputDirectory; - var blazorPublishDirectory = Path.Combine(publishDirectory, Path.GetFileNameWithoutExtension(project.ProjectFilePath)); - - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output. - - var bootJsonPath = Path.Combine(blazorPublishDirectory, "dist", "_framework", "blazor.boot.json"); - Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\""); - Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); - } - - [Fact] - public async Task Publish_HostedApp_Works() - { - // Arrange - using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", }); - project.TargetFramework = "netcoreapp3.1"; - var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish"); - - Assert.BuildPassed(result); - - var publishDirectory = project.PublishOutputDirectory; - // Make sure the main project exists - Assert.FileExists(result, publishDirectory, "blazorhosted.dll"); - - var blazorPublishDirectory = Path.Combine(publishDirectory, "standalone"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. - - // Verify static assets are in the publish directory - Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); - - // Verify static web assets from referenced projects are copied. - Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); - Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css"); - - // Verify static assets are in the publish directory - Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); - - // Verify web.config - Assert.FileExists(result, publishDirectory, "web.config"); - - var blazorConfig = Path.Combine(result.Project.DirectoryPath, publishDirectory, "standalone.blazor.config"); - var blazorConfigLines = File.ReadAllLines(blazorConfig); - Assert.Equal(".", blazorConfigLines[0]); - Assert.Equal("standalone/", blazorConfigLines[1]); - } - - [Fact] - public async Task Publish_HostedApp_WithNoBuild_Works() - { - // Arrange - using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", }); - project.TargetFramework = "netcoreapp3.1"; - var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build"); - - Assert.BuildPassed(result); - - result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:NoBuild=true"); - - var publishDirectory = project.PublishOutputDirectory; - // Make sure the main project exists - Assert.FileExists(result, publishDirectory, "blazorhosted.dll"); - - var blazorPublishDirectory = Path.Combine(publishDirectory, "standalone"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.boot.json"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "blazor.webassembly.js"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.wasm"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "wasm", "dotnet.js"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "standalone.dll"); - Assert.FileExists(result, blazorPublishDirectory, "dist", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. - - // Verify static assets are in the publish directory - Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); - - // Verify static web assets from referenced projects are copied. - // Uncomment once https://github.com/aspnet/AspNetCore/issues/17426 is resolved. - // Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); - // Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css"); - - // Verify static assets are in the publish directory - Assert.FileExists(result, blazorPublishDirectory, "dist", "index.html"); - - // Verify web.config - Assert.FileExists(result, publishDirectory, "web.config"); - } - } -} diff --git a/src/Components/Blazor/Build/test/ChildContentRazorIntegrationTest.cs b/src/Components/Blazor/Build/test/ChildContentRazorIntegrationTest.cs deleted file mode 100644 index 720c00fe9be5cbb94199f117740ce5fa7ac6fe36..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/ChildContentRazorIntegrationTest.cs +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Test.Helpers; -using Microsoft.CodeAnalysis.CSharp; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.AspNetCore.Blazor.Build.Test -{ - public class ChildContentRazorIntegrationTest : RazorIntegrationTestBase - { - private readonly CSharpSyntaxTree RenderChildContentComponent = Parse(@" -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Rendering; -namespace Test -{ - public class RenderChildContent : ComponentBase - { - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - builder.AddContent(0, ChildContent); - } - - [Parameter] - public RenderFragment ChildContent { get; set; } - } -} -"); - - private readonly CSharpSyntaxTree RenderChildContentStringComponent = Parse(@" -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Rendering; -namespace Test -{ - public class RenderChildContentString : ComponentBase - { - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - builder.AddContent(0, ChildContent, Value); - } - - [Parameter] - public RenderFragment<string> ChildContent { get; set; } - - [Parameter] - public string Value { get; set; } - } -} -"); - - private readonly CSharpSyntaxTree RenderMultipleChildContent = Parse(@" -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Rendering; -namespace Test -{ - public class RenderMultipleChildContent : ComponentBase - { - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - builder.AddContent(0, Header, Name); - builder.AddContent(1, ChildContent, Value); - builder.AddContent(2, Footer); - } - - [Parameter] - public string Name { get; set; } - - [Parameter] - public RenderFragment<string> Header { get; set; } - - [Parameter] - public RenderFragment<string> ChildContent { get; set; } - - [Parameter] - public RenderFragment Footer { get; set; } - - [Parameter] - public string Value { get; set; } - } -} -"); - - public ChildContentRazorIntegrationTest(ITestOutputHelper output) - : base(output) - { - } - - internal override bool UseTwoPhaseCompilation => true; - - [Fact] - public void Render_BodyChildContent() - { - // Arrange - AdditionalSyntaxTrees.Add(RenderChildContentComponent); - - var component = CompileToComponent(@" -<RenderChildContent> - <div></div> -</RenderChildContent>"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 0), - frame => AssertFrame.Attribute(frame, "ChildContent", 1), - frame => AssertFrame.Markup(frame, "\n <div></div>\n", 2)); - } - - [Fact] - public void Render_BodyChildContent_Generic() - { - // Arrange - AdditionalSyntaxTrees.Add(RenderChildContentStringComponent); - - var component = CompileToComponent(@" -<RenderChildContentString Value=""HI""> - <div>@context.ToLowerInvariant()</div> -</RenderChildContentString>"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.RenderChildContentString", 3, 0), - frame => AssertFrame.Attribute(frame, "Value", "HI", 1), - frame => AssertFrame.Attribute(frame, "ChildContent", 2), - frame => AssertFrame.MarkupWhitespace(frame, 3), - frame => AssertFrame.Element(frame, "div", 2, 4), - frame => AssertFrame.Text(frame, "hi", 5), - frame => AssertFrame.MarkupWhitespace(frame, 6)); - } - - [Fact] - public void Render_ExplicitChildContent() - { - // Arrange - AdditionalSyntaxTrees.Add(RenderChildContentComponent); - - var component = CompileToComponent(@" -<RenderChildContent> - <ChildContent> - <div></div> - </ChildContent> -</RenderChildContent>"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 0), - frame => AssertFrame.Attribute(frame, "ChildContent", 1), - frame => AssertFrame.Markup(frame, "\n <div></div>\n ", 2)); - } - - [Fact] - public void Render_BodyChildContent_Recursive() - { - // Arrange - AdditionalSyntaxTrees.Add(RenderChildContentComponent); - - var component = CompileToComponent(@" - -<RenderChildContent> - <RenderChildContent> - <div></div> - </RenderChildContent> -</RenderChildContent>"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 0), - frame => AssertFrame.Attribute(frame, "ChildContent", 1), - frame => AssertFrame.MarkupWhitespace(frame, 2), - frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 3), - frame => AssertFrame.Attribute(frame, "ChildContent", 4), - frame => AssertFrame.MarkupWhitespace(frame, 6), - frame => AssertFrame.Markup(frame, "\n <div></div>\n ", 5)); - } - - [Fact] - public void Render_AttributeChildContent() - { - // Arrange - AdditionalSyntaxTrees.Add(RenderChildContentComponent); - - var component = CompileToComponent(@" -@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; } -<RenderChildContent ChildContent=""@template(""HI"")"" />"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2), - frame => AssertFrame.Attribute(frame, "ChildContent", 3), - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "hi", 1)); - } - - [Fact] - public void Render_AttributeChildContent_RenderFragmentOfString() - { - // Arrange - AdditionalSyntaxTrees.Add(RenderChildContentStringComponent); - - var component = CompileToComponent(@" -@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; } -<RenderChildContentString ChildContent=""@template"" Value=""HI"" />"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.RenderChildContentString", 3, 2), - frame => AssertFrame.Attribute(frame, "ChildContent", 3), - frame => AssertFrame.Attribute(frame, "Value", "HI", 4), - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "hi", 1)); - } - - [Fact] - public void Render_AttributeChildContent_NoArgTemplate() - { - // Arrange - AdditionalSyntaxTrees.Add(RenderChildContentComponent); - - var component = CompileToComponent(@" -@{ RenderFragment template = @<div>@(""HI"".ToLowerInvariant())</div>; } -<RenderChildContent ChildContent=""@template"" />"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2), - frame => AssertFrame.Attribute(frame, "ChildContent", 3), - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "hi", 1)); - } - - [Fact] - public void Render_AttributeChildContent_IgnoresEmptyBody() - { - // Arrange - AdditionalSyntaxTrees.Add(RenderChildContentComponent); - - var component = CompileToComponent(@" -@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; } -<RenderChildContent ChildContent=""@template(""HI"")""></RenderChildContent>"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2), - frame => AssertFrame.Attribute(frame, "ChildContent", 3), - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "hi", 1)); - } - - [Fact] - public void Render_AttributeChildContent_IgnoresWhitespaceBody() - { - // Arrange - AdditionalSyntaxTrees.Add(RenderChildContentComponent); - - var component = CompileToComponent(@" -@{ RenderFragment<string> template = (context) => @<div>@context.ToLowerInvariant()</div>; } -<RenderChildContent ChildContent=""@template(""HI"")""> - -</RenderChildContent>"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.RenderChildContent", 2, 2), - frame => AssertFrame.Attribute(frame, "ChildContent", 3), - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "hi", 1)); - } - - [Fact] - public void Render_MultipleChildContent() - { - // Arrange - AdditionalSyntaxTrees.Add(RenderMultipleChildContent); - - var component = CompileToComponent(@" -@{ RenderFragment<string> header = context => @<div>@context.ToLowerInvariant()</div>; } -<RenderMultipleChildContent Name=""billg"" Header=@header Value=""HI""> - <ChildContent>Some @context.ToLowerInvariant() Content</ChildContent> - <Footer>Bye!</Footer> -</RenderMultipleChildContent>"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.RenderMultipleChildContent", 6, 2), - frame => AssertFrame.Attribute(frame, "Name", "billg", 3), - frame => AssertFrame.Attribute(frame, "Header", typeof(RenderFragment<string>), 4), - frame => AssertFrame.Attribute(frame, "Value", "HI", 5), - frame => AssertFrame.Attribute(frame, "ChildContent", typeof(RenderFragment<string>), 6), - frame => AssertFrame.Attribute(frame, "Footer", typeof(RenderFragment), 10), - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "billg", 1), - frame => AssertFrame.Text(frame, "Some ", 7), - frame => AssertFrame.Text(frame, "hi", 8), - frame => AssertFrame.Text(frame, " Content", 9), - frame => AssertFrame.Text(frame, "Bye!", 11)); - } - - [Fact] - public void Render_MultipleChildContent_ContextParameterOnComponent() - { - // Arrange - AdditionalSyntaxTrees.Add(RenderMultipleChildContent); - - var component = CompileToComponent(@" -<RenderMultipleChildContent Name=""billg"" Value=""HI"" Context=""item""> - <Header><div>@item.ToLowerInvariant()</div></Header> - <ChildContent Context=""Context"">Some @Context.ToLowerInvariant() Content</ChildContent> - <Footer>Bye!</Footer> -</RenderMultipleChildContent>"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.RenderMultipleChildContent", 6, 0), - frame => AssertFrame.Attribute(frame, "Name", "billg", 1), - frame => AssertFrame.Attribute(frame, "Value", "HI", 2), - frame => AssertFrame.Attribute(frame, "Header", typeof(RenderFragment<string>), 3), - frame => AssertFrame.Attribute(frame, "ChildContent", typeof(RenderFragment<string>), 6), - frame => AssertFrame.Attribute(frame, "Footer", typeof(RenderFragment), 10), - frame => AssertFrame.Element(frame, "div", 2, 4), - frame => AssertFrame.Text(frame, "billg", 5), - frame => AssertFrame.Text(frame, "Some ", 7), - frame => AssertFrame.Text(frame, "hi", 8), - frame => AssertFrame.Text(frame, " Content", 9), - frame => AssertFrame.Text(frame, "Bye!", 11)); - } - - // Verifies that our check for reuse of parameter names isn't too aggressive. - [Fact] - public void Render_MultipleChildContent_ContextParameterOnComponent_SetsSameName() - { - // Arrange - AdditionalSyntaxTrees.Add(RenderMultipleChildContent); - - var component = CompileToComponent(@" - -<RenderMultipleChildContent Name=""billg"" Value=""HI"" Context=""item""> - <Header><div>@item.ToLowerInvariant()</div></Header> - <ChildContent Context=""item"">Some @item.ToLowerInvariant() Content</ChildContent> - <Footer>Bye!</Footer> -</RenderMultipleChildContent>"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.RenderMultipleChildContent", 6, 0), - frame => AssertFrame.Attribute(frame, "Name", "billg", 1), - frame => AssertFrame.Attribute(frame, "Value", "HI", 2), - frame => AssertFrame.Attribute(frame, "Header", typeof(RenderFragment<string>), 3), - frame => AssertFrame.Attribute(frame, "ChildContent", typeof(RenderFragment<string>), 6), - frame => AssertFrame.Attribute(frame, "Footer", typeof(RenderFragment), 10), - frame => AssertFrame.Element(frame, "div", 2, 4), - frame => AssertFrame.Text(frame, "billg", 5), - frame => AssertFrame.Text(frame, "Some ", 7), - frame => AssertFrame.Text(frame, "hi", 8), - frame => AssertFrame.Text(frame, " Content", 9), - frame => AssertFrame.Text(frame, "Bye!", 11)); - } - } -} diff --git a/src/Components/Blazor/Build/test/ComponentRenderingRazorIntegrationTest.cs b/src/Components/Blazor/Build/test/ComponentRenderingRazorIntegrationTest.cs deleted file mode 100644 index d15cf4f5849961c2d0f1cd569afe7d41e665fd25..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/ComponentRenderingRazorIntegrationTest.cs +++ /dev/null @@ -1,616 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.RenderTree; -using Microsoft.AspNetCore.Components.Test.Helpers; -using Microsoft.AspNetCore.Components.Web; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.AspNetCore.Blazor.Build.Test -{ - public class ComponentRenderingRazorIntegrationTest : RazorIntegrationTestBase - { - public ComponentRenderingRazorIntegrationTest(ITestOutputHelper output) - : base(output) - { - } - - internal override bool UseTwoPhaseCompilation => true; - - [Fact] - public void Render_ChildComponent_Simple() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using Microsoft.AspNetCore.Components; - -namespace Test -{ - public class MyComponent : ComponentBase - { - } -} -")); - - var component = CompileToComponent(@" -<MyComponent/>"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.MyComponent", 1, 0)); - } - - [Fact] - public void Render_ChildComponent_WithParameters() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using Microsoft.AspNetCore.Components; - -namespace Test -{ - public class SomeType - { - } - - public class MyComponent : ComponentBase - { - [Parameter] public int IntProperty { get; set; } - [Parameter] public bool BoolProperty { get; set; } - [Parameter] public string StringProperty { get; set; } - [Parameter] public SomeType ObjectProperty { get; set; } - } -} -")); - - var component = CompileToComponent(@" -<MyComponent - IntProperty=""123"" - BoolProperty=""true"" - StringProperty=""My string"" - ObjectProperty=""new SomeType()"" />"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.MyComponent", 5, 0), - frame => AssertFrame.Attribute(frame, "IntProperty", 123, 1), - frame => AssertFrame.Attribute(frame, "BoolProperty", true, 2), - frame => AssertFrame.Attribute(frame, "StringProperty", "My string", 3), - frame => - { - AssertFrame.Attribute(frame, "ObjectProperty", 4); - Assert.Equal("Test.SomeType", frame.AttributeValue.GetType().FullName); - }); - } - - [Fact] - public void Render_ChildComponent_TriesToSetNonParamter() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using Microsoft.AspNetCore.Components; - -namespace Test -{ - public class MyComponent : ComponentBase - { - public int IntProperty { get; set; } - } -} -")); - - var component = CompileToComponent(@" -<MyComponent IntProperty=""123"" />"); - - // Act - var ex = Assert.Throws<InvalidOperationException>(() => GetRenderTree(component)); - - // Assert - Assert.Equal( - "Object of type 'Test.MyComponent' has a property matching the name 'IntProperty', " + - "but it does not have [ParameterAttribute] or [CascadingParameterAttribute] applied.", - ex.Message); - } - - [Fact] - public void Render_ChildComponent_WithExplicitStringParameter() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using Microsoft.AspNetCore.Components; - -namespace Test -{ - public class MyComponent : ComponentBase - { - [Parameter] - public string StringProperty { get; set; } - } -} -")); - - var component = CompileToComponent(@" -<MyComponent StringProperty=""@(42.ToString())"" />"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0), - frame => AssertFrame.Attribute(frame, "StringProperty", "42", 1)); - } - - [Fact] - public void Render_ChildComponent_WithNonPropertyAttributes() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; - -namespace Test -{ - public class MyComponent : ComponentBase, IComponent - { - Task IComponent.SetParametersAsync(ParameterView parameters) - { - return Task.CompletedTask; - } - } -} -")); - - var component = CompileToComponent(@" -<MyComponent some-attribute=""foo"" another-attribute=""@(42.ToString())"" />"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0), - frame => AssertFrame.Attribute(frame, "some-attribute", "foo", 1), - frame => AssertFrame.Attribute(frame, "another-attribute", "42", 2)); - } - - - [Theory] - [InlineData("e => Increment(e)")] - [InlineData("(e) => Increment(e)")] - [InlineData("@(e => Increment(e))")] - [InlineData("@(e => { Increment(e); })")] - [InlineData("Increment")] - [InlineData("@Increment")] - [InlineData("@(Increment)")] - public void Render_ChildComponent_WithEventHandler(string expression) - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using System; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Web; - -namespace Test -{ - public class MyComponent : ComponentBase - { - [Parameter] - public Action<MouseEventArgs> OnClick { get; set; } - } -} -")); - - var component = CompileToComponent($@" -@using Microsoft.AspNetCore.Components.Web -<MyComponent OnClick=""{expression}""/> - -@code {{ - private int counter; - private void Increment(MouseEventArgs e) {{ - counter++; - }} -}}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0), - frame => - { - AssertFrame.Attribute(frame, "OnClick", 1); - - // The handler will have been assigned to a lambda - var handler = Assert.IsType<Action<MouseEventArgs>>(frame.AttributeValue); - Assert.Equal("Test.TestComponent", handler.Target.GetType().FullName); - }); - } - - [Fact] - public void Render_ChildComponent_WithExplicitEventHandler() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using System; -using Microsoft.AspNetCore.Components; - -namespace Test -{ - public class MyComponent : ComponentBase - { - [Parameter] - public Action<EventArgs> OnClick { get; set; } - } -} -")); - - var component = CompileToComponent(@" -<MyComponent OnClick=""@Increment""/> - -@code { - private int counter; - private void Increment(EventArgs e) { - counter++; - } -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0), - frame => - { - AssertFrame.Attribute(frame, "OnClick", 1); - - // The handler will have been assigned to a lambda - var handler = Assert.IsType<Action<EventArgs>>(frame.AttributeValue); - Assert.Equal("Test.TestComponent", handler.Target.GetType().FullName); - Assert.Equal("Increment", handler.Method.Name); - }); - } - - [Fact] - public void Render_ChildComponent_WithMinimizedBoolAttribute() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using Microsoft.AspNetCore.Components; - -namespace Test -{ - public class MyComponent : ComponentBase - { - [Parameter] - public bool BoolProperty { get; set; } - } -}")); - - var component = CompileToComponent(@" -<MyComponent BoolProperty />"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0), - frame => AssertFrame.Attribute(frame, "BoolProperty", true, 1)); - } - - [Fact] - public void Render_ChildComponent_WithChildContent() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using Microsoft.AspNetCore.Components; -namespace Test -{ - public class MyComponent : ComponentBase - { - [Parameter] - public string MyAttr { get; set; } - - [Parameter] - public RenderFragment ChildContent { get; set; } - } -} -")); - - var component = CompileToComponent(@" -<MyComponent MyAttr=""abc"">Some text<some-child a='1'>Nested text @(""Hello"")</some-child></MyComponent>"); - - // Act - var frames = GetRenderTree(component); - - // Assert: component frames are correct - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0), - frame => AssertFrame.Attribute(frame, "MyAttr", "abc", 1), - frame => AssertFrame.Attribute(frame, "ChildContent", 2)); - - // Assert: Captured ChildContent frames are correct - var childFrames = GetFrames((RenderFragment)frames[2].AttributeValue); - Assert.Collection( - childFrames.AsEnumerable(), - frame => AssertFrame.Text(frame, "Some text", 3), - frame => AssertFrame.Element(frame, "some-child", 4, 4), - frame => AssertFrame.Attribute(frame, "a", "1", 5), - frame => AssertFrame.Text(frame, "Nested text ", 6), - frame => AssertFrame.Text(frame, "Hello", 7)); - } - - [Fact] - public void Render_ChildComponent_Nested() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using Microsoft.AspNetCore.Components; - -namespace Test -{ - public class MyComponent : ComponentBase - { - [Parameter] - public RenderFragment ChildContent { get; set; } - } -} -")); - - var component = CompileToComponent(@" -<MyComponent><MyComponent>Some text</MyComponent></MyComponent>"); - - // Act - var frames = GetRenderTree(component); - - // Assert: outer component frames are correct - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0), - frame => AssertFrame.Attribute(frame, "ChildContent", 1)); - - // Assert: first level of ChildContent is correct - // Note that we don't really need the sequence numbers to continue on from the - // sequence numbers at the parent level. All that really matters is that they are - // correct relative to each other (i.e., incrementing) within the nesting level. - // As an implementation detail, it happens that they do follow on from the parent - // level, but we could change that part of the implementation if we wanted. - var innerFrames = GetFrames((RenderFragment)frames[1].AttributeValue).AsEnumerable().ToArray(); - Assert.Collection( - innerFrames, - frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 2), - frame => AssertFrame.Attribute(frame, "ChildContent", 3)); - - // Assert: second level of ChildContent is correct - Assert.Collection( - GetFrames((RenderFragment)innerFrames[1].AttributeValue).AsEnumerable(), - frame => AssertFrame.Text(frame, "Some text", 4)); - } - - [Fact] // https://github.com/aspnet/Blazor/issues/773 - public void Regression_773() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using Microsoft.AspNetCore.Components; - -namespace Test -{ - public class SurveyPrompt : ComponentBase - { - [Parameter] public string Title { get; set; } - } -} -")); - - var component = CompileToComponent(@" -@page ""/"" - -<SurveyPrompt Title=""<div>Test!</div>"" /> -"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.SurveyPrompt", 2, 0), - frame => AssertFrame.Attribute(frame, "Title", "<div>Test!</div>", 1)); - } - - - [Fact] - public void Regression_784() - { - // Arrange - - // Act - var component = CompileToComponent(@" -@using Microsoft.AspNetCore.Components.Web -<p @onmouseover=""OnComponentHover"" style=""background: @ParentBgColor;"" /> -@code { - public string ParentBgColor { get; set; } = ""#FFFFFF""; - - public void OnComponentHover(MouseEventArgs e) - { - } -} -"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "p", 3, 0), - frame => AssertFrame.Attribute(frame, "onmouseover", 1), - frame => AssertFrame.Attribute(frame, "style", "background: #FFFFFF;", 2)); - } - - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/6185")] - public void Render_Component_HtmlEncoded() - { - // Arrange - var component = CompileToComponent(@"<span>Hi</span>"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Text(frame, "<span>Hi</span>")); - } - - [Fact] - public void Render_Component_HtmlBlockEncoded() - { - // Arrange - var component = CompileToComponent(@"<div><span>Hi</span></div>"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Markup(frame, "<div><span>Hi</span></div>")); - } - - // Integration test for HTML block rewriting - [Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/6183")] - public void Render_HtmlBlock_Integration() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using Microsoft.AspNetCore.Components; -namespace Test -{ - public class MyComponent : ComponentBase - { - [Parameter] - public RenderFragment ChildContent { get; set; } - } -} -")); - - var component = CompileToComponent(@" -<html> - <head><meta><meta></head> - <body> - <MyComponent> - <div><span></span><span></span></div> - <div>@(""hi"")</div> - <div><span></span><span></span></div> - <div></div> - <div>@(""hi"")</div> - <div></div> - </MyComponent> - </body> -</html>"); - - // Act - var frames = GetRenderTree(component); - - // Assert: component frames are correct - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "html", 9, 0), - frame => AssertFrame.MarkupWhitespace(frame, 1), - frame => AssertFrame.Markup(frame, "<head><meta><meta></head>\n ", 2), - frame => AssertFrame.Element(frame, "body", 5, 3), - frame => AssertFrame.MarkupWhitespace(frame, 4), - frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 5), - frame => AssertFrame.Attribute(frame, "ChildContent", 6), - frame => AssertFrame.MarkupWhitespace(frame, 16), - frame => AssertFrame.MarkupWhitespace(frame, 17)); - - // Assert: Captured ChildContent frames are correct - var childFrames = GetFrames((RenderFragment)frames[6].AttributeValue); - Assert.Collection( - childFrames.AsEnumerable(), - frame => AssertFrame.MarkupWhitespace(frame, 7), - frame => AssertFrame.Markup(frame, "<div><span></span><span></span></div>\n ", 8), - frame => AssertFrame.Element(frame, "div", 2, 9), - frame => AssertFrame.Text(frame, "hi", 10), - frame => AssertFrame.MarkupWhitespace(frame, 11), - frame => AssertFrame.Markup(frame, "<div><span></span><span></span></div>\n <div></div>\n ", 12), - frame => AssertFrame.Element(frame, "div", 2, 13), - frame => AssertFrame.Text(frame, "hi", 14), - frame => AssertFrame.Markup(frame, "\n <div></div>\n ", 15)); - } - - [Fact] - public void RazorTemplate_CanBeUsedFromComponent() - { - // Arrange - AdditionalSyntaxTrees.Add(Parse(@" -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Rendering; - -namespace Test -{ - public class Repeater : ComponentBase - { - [Parameter] public int Count { get; set; } - [Parameter] public RenderFragment<string> Template { get; set; } - [Parameter] public string Value { get; set; } - - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - for (var i = 0; i < Count; i++) - { - builder.AddContent(i, Template, Value); - } - } - } -} -")); - - var component = CompileToComponent(@" -@{ RenderFragment<string> template = (context) => @<div>@context.ToLower()</div>; } -<Repeater Count=3 Value=""Hello, World!"" Template=""template"" /> -"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, "Test.Repeater", 4, 2), - frame => AssertFrame.Attribute(frame, "Count", typeof(int), 3), - frame => AssertFrame.Attribute(frame, "Value", typeof(string), 4), - frame => AssertFrame.Attribute(frame, "Template", typeof(RenderFragment<string>), 5), - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "hello, world!", 1), - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "hello, world!", 1), - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "hello, world!", 1)); - } - } -} diff --git a/src/Components/Blazor/Build/test/DirectiveRazorIntegrationTest.cs b/src/Components/Blazor/Build/test/DirectiveRazorIntegrationTest.cs deleted file mode 100644 index ef46dd1d196c72695fd6877a2fbb1a6285195d51..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/DirectiveRazorIntegrationTest.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Test.Helpers; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.AspNetCore.Blazor.Build.Test -{ - // Integration tests for Blazor's directives - public class DirectiveRazorIntegrationTest : RazorIntegrationTestBase - { - public DirectiveRazorIntegrationTest(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void ComponentsDoNotHaveLayoutAttributeByDefault() - { - // Arrange/Act - var component = CompileToComponent($"Hello"); - - // Assert - Assert.Null(component.GetType().GetCustomAttribute<LayoutAttribute>()); - } - - [Fact] - public void SupportsLayoutDeclarations() - { - // Arrange/Act - var testComponentTypeName = FullTypeName<TestLayout>(); - var component = CompileToComponent( - $"@layout {testComponentTypeName}\n" + - $"Hello"); - var frames = GetRenderTree(component); - - // Assert - var layoutAttribute = component.GetType().GetCustomAttribute<LayoutAttribute>(); - Assert.NotNull(layoutAttribute); - Assert.Equal(typeof(TestLayout), layoutAttribute.LayoutType); - Assert.Collection(frames, - frame => AssertFrame.Text(frame, "Hello")); - } - - [Fact] - public void SupportsImplementsDeclarations() - { - // Arrange/Act - var testInterfaceTypeName = FullTypeName<ITestInterface>(); - var component = CompileToComponent( - $"@implements {testInterfaceTypeName}\n" + - $"Hello"); - var frames = GetRenderTree(component); - - // Assert - Assert.IsAssignableFrom<ITestInterface>(component); - Assert.Collection(frames, - frame => AssertFrame.Text(frame, "Hello")); - } - - [Fact] - public void SupportsMultipleImplementsDeclarations() - { - // Arrange/Act - var testInterfaceTypeName = FullTypeName<ITestInterface>(); - var testInterfaceTypeName2 = FullTypeName<ITestInterface2>(); - var component = CompileToComponent( - $"@implements {testInterfaceTypeName}\n" + - $"@implements {testInterfaceTypeName2}\n" + - $"Hello"); - var frames = GetRenderTree(component); - - // Assert - Assert.IsAssignableFrom<ITestInterface>(component); - Assert.IsAssignableFrom<ITestInterface2>(component); - Assert.Collection(frames, - frame => AssertFrame.Text(frame, "Hello")); - } - - [Fact] - public void SupportsInheritsDirective() - { - // Arrange/Act - var testBaseClassTypeName = FullTypeName<TestBaseClass>(); - var component = CompileToComponent( - $"@inherits {testBaseClassTypeName}" + Environment.NewLine + - $"Hello"); - var frames = GetRenderTree(component); - - // Assert - Assert.IsAssignableFrom<TestBaseClass>(component); - Assert.Collection(frames, - frame => AssertFrame.Text(frame, "Hello")); - } - - [Fact] - public void SupportsInjectDirective() - { - // Arrange/Act 1: Compilation - var componentType = CompileToComponent( - $"@inject {FullTypeName<IMyService1>()} MyService1\n" + - $"@inject {FullTypeName<IMyService2>()} MyService2\n" + - $"Hello from @MyService1 and @MyService2").GetType(); - - // Assert 1: Compiled type has correct properties - var propertyFlags = BindingFlags.Instance | BindingFlags.NonPublic; - var injectableProperties = componentType.GetProperties(propertyFlags) - .Where(p => p.GetCustomAttribute<InjectAttribute>() != null); - Assert.Collection(injectableProperties.OrderBy(p => p.Name), - property => - { - Assert.Equal("MyService1", property.Name); - Assert.Equal(typeof(IMyService1), property.PropertyType); - Assert.False(property.GetMethod.IsPublic); - Assert.False(property.SetMethod.IsPublic); - }, - property => - { - Assert.Equal("MyService2", property.Name); - Assert.Equal(typeof(IMyService2), property.PropertyType); - Assert.False(property.GetMethod.IsPublic); - Assert.False(property.SetMethod.IsPublic); - }); - - // Arrange/Act 2: DI-supplied component has correct behavior - var serviceProvider = new TestServiceProvider(); - serviceProvider.AddService<IMyService1>(new MyService1Impl()); - serviceProvider.AddService<IMyService2>(new MyService2Impl()); - var componentFactory = new ComponentFactory(); - var component = componentFactory.InstantiateComponent(serviceProvider, componentType); - var frames = GetRenderTree(component); - - // Assert 2: Rendered component behaves correctly - Assert.Collection(frames, - frame => AssertFrame.Text(frame, "Hello from "), - frame => AssertFrame.Text(frame, typeof(MyService1Impl).FullName), - frame => AssertFrame.Text(frame, " and "), - frame => AssertFrame.Text(frame, typeof(MyService2Impl).FullName)); - } - - public class TestLayout : IComponent - { - [Parameter] - public RenderFragment Body { get; set; } - - public void Attach(RenderHandle renderHandle) - { - } - - public Task SetParametersAsync(ParameterView parameters) - { - return Task.CompletedTask; - } - } - - public interface ITestInterface { } - - public interface ITestInterface2 { } - - public class TestBaseClass : ComponentBase { } - - public interface IMyService1 { } - public interface IMyService2 { } - public class MyService1Impl : IMyService1 { } - public class MyService2Impl : IMyService2 { } - } -} diff --git a/src/Components/Blazor/Build/test/GenericComponentRazorIntegrationTest.cs b/src/Components/Blazor/Build/test/GenericComponentRazorIntegrationTest.cs deleted file mode 100644 index 7527e83535ca321504f0bfc825598b6aa66378ca..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/GenericComponentRazorIntegrationTest.cs +++ /dev/null @@ -1,314 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Test.Helpers; -using Microsoft.CodeAnalysis.CSharp; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.AspNetCore.Blazor.Build.Test -{ - public class GenericComponentRazorIntegrationTest : RazorIntegrationTestBase - { - private readonly CSharpSyntaxTree GenericContextComponent = Parse(@" -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Rendering; -namespace Test -{ - public class GenericContext<TItem> : ComponentBase - { - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - var items = (IReadOnlyList<TItem>)Items ?? Array.Empty<TItem>(); - for (var i = 0; i < items.Count; i++) - { - if (ChildContent == null) - { - builder.AddContent(i, Items[i]); - } - else - { - builder.AddContent(i, ChildContent, new Context() { Index = i, Item = items[i], }); - } - } - } - - [Parameter] - public List<TItem> Items { get; set; } - - [Parameter] - public RenderFragment<Context> ChildContent { get; set; } - - public class Context - { - public int Index { get; set; } - public TItem Item { get; set; } - } - } -} -"); - - private readonly CSharpSyntaxTree MultipleGenericParameterComponent = Parse(@" -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Rendering; -namespace Test -{ - public class MultipleGenericParameter<TItem1, TItem2, TItem3> : ComponentBase - { - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - builder.AddContent(0, Item1); - builder.AddContent(1, Item2); - builder.AddContent(2, Item3); - } - - [Parameter] - public TItem1 Item1 { get; set; } - - [Parameter] - public TItem2 Item2 { get; set; } - - [Parameter] - public TItem3 Item3 { get; set; } - } -} -"); - - public GenericComponentRazorIntegrationTest(ITestOutputHelper output) - : base(output) - { - } - - internal override bool UseTwoPhaseCompilation => true; - - [Fact] - public void Render_GenericComponent_WithoutChildContent() - { - // Arrange - AdditionalSyntaxTrees.Add(GenericContextComponent); - - var component = CompileToComponent(@" -<GenericContext TItem=int Items=""@(new List<int>() { 1, 2, })"" />"); - - // Act - var frames = GetRenderTree(component); - - // Assert - var genericComponentType = component.GetType().Assembly.DefinedTypes - .Where(t => t.Name == "GenericContext`1") - .Single() - .MakeGenericType(typeof(int)); - - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, genericComponentType.FullName, 2, 0), - frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1), - frame => AssertFrame.Text(frame, "1", 0), - frame => AssertFrame.Text(frame, "2", 1)); - } - - [Fact] - public void Render_GenericComponent_WithRef() - { - // Arrange - AdditionalSyntaxTrees.Add(GenericContextComponent); - - var component = CompileToComponent(@" -<GenericContext TItem=int Items=""@(new List<int>() { 1, 2, })"" @ref=""_my"" /> - -@code { - GenericContext<int> _my; - void Foo() { GC.KeepAlive(_my); } -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - var genericComponentType = component.GetType().Assembly.DefinedTypes - .Where(t => t.Name == "GenericContext`1") - .Single() - .MakeGenericType(typeof(int)); - - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0), - frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1), - frame => AssertFrame.ComponentReferenceCapture(frame, 2), - frame => AssertFrame.Text(frame, "1", 0), - frame => AssertFrame.Text(frame, "2", 1)); - } - - [Fact] - public void Render_GenericComponent_WithChildContent() - { - // Arrange - AdditionalSyntaxTrees.Add(GenericContextComponent); - - var component = CompileToComponent(@" -<GenericContext TItem=int Items=""@(new List<int>() { 1, 2, })""> - <div>@(context.Item * context.Index)</div> -</GenericContext>"); - - // Act - var frames = GetRenderTree(component); - - // Assert - var genericComponentType = component.GetType().Assembly.DefinedTypes - .Where(t => t.Name == "GenericContext`1") - .Single() - .MakeGenericType(typeof(int)); - - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0), - frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1), - frame => AssertFrame.Attribute(frame, "ChildContent", 2), - frame => AssertFrame.MarkupWhitespace(frame, 3), - frame => AssertFrame.Element(frame, "div", 2, 4), - frame => AssertFrame.Text(frame, "0", 5), - frame => AssertFrame.MarkupWhitespace(frame, 6), - frame => AssertFrame.MarkupWhitespace(frame, 3), - frame => AssertFrame.Element(frame, "div", 2, 4), - frame => AssertFrame.Text(frame, "2", 5), - frame => AssertFrame.MarkupWhitespace(frame, 6)); - } - - [Fact] - public void Render_GenericComponent_TypeInference_WithRef() - { - // Arrange - AdditionalSyntaxTrees.Add(GenericContextComponent); - - var component = CompileToComponent(@" -<GenericContext Items=""@(new List<int>() { 1, 2, })"" @ref=""_my"" /> - -@code { - GenericContext<int> _my; - void Foo() { GC.KeepAlive(_my); } -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - var genericComponentType = component.GetType().Assembly.DefinedTypes - .Where(t => t.Name == "GenericContext`1") - .Single() - .MakeGenericType(typeof(int)); - - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0), - frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1), - frame => AssertFrame.ComponentReferenceCapture(frame, 2), - frame => AssertFrame.Text(frame, "1", 0), - frame => AssertFrame.Text(frame, "2", 1)); - } - - [Fact] - public void Render_GenericComponent_TypeInference_WithRef_Recursive() - { - // Arrange - AdditionalSyntaxTrees.Add(GenericContextComponent); - - var assembly = CompileToAssembly("Test.cshtml", @" -@typeparam TItem -<GenericContext Items=""@MyItems"" @ref=""_my"" /> - -@code { - [Parameter] public List<TItem> MyItems { get; set; } - GenericContext<TItem> _my; - void Foo() { GC.KeepAlive(_my); } -}"); - - var componentType = assembly.Assembly.DefinedTypes - .Where(t => t.Name == "Test`1") - .Single() - .MakeGenericType(typeof(int)); - var component = (IComponent)Activator.CreateInstance(componentType); - - // Act - var frames = GetRenderTree(component); - - // Assert - var genericComponentType = assembly.Assembly.DefinedTypes - .Where(t => t.Name == "GenericContext`1") - .Single() - .MakeGenericType(typeof(int)); - - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, genericComponentType.FullName, 3, 0), - frame => AssertFrame.Attribute(frame, "Items", 1), - frame => AssertFrame.ComponentReferenceCapture(frame, 2)); - } - - [Fact] - public void Render_GenericComponent_TypeInference_WithoutChildContent() - { - // Arrange - AdditionalSyntaxTrees.Add(GenericContextComponent); - - var component = CompileToComponent(@" -<GenericContext Items=""@(new List<int>() { 1, 2, })"" />"); - - // Act - var frames = GetRenderTree(component); - - // Assert - var genericComponentType = component.GetType().Assembly.DefinedTypes - .Where(t => t.Name == "GenericContext`1") - .Single() - .MakeGenericType(typeof(int)); - - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, genericComponentType.FullName, 2, 0), - frame => AssertFrame.Attribute(frame, "Items", typeof(List<int>), 1), - frame => AssertFrame.Text(frame, "1", 0), - frame => AssertFrame.Text(frame, "2", 1)); - } - - [Fact] - public void Render_GenericComponent_MultipleParameters_WithChildContent() - { - // Arrange - AdditionalSyntaxTrees.Add(MultipleGenericParameterComponent); - - var component = CompileToComponent(@" -<MultipleGenericParameter - TItem1=""int"" - TItem2=""string"" - TItem3=long - Item1=3 - Item2=""@(""FOO"")"" - Item3=39L/>"); - - // Act - var frames = GetRenderTree(component); - - // Assert - var genericComponentType = component.GetType().Assembly.DefinedTypes - .Where(t => t.Name == "MultipleGenericParameter`3") - .Single() - .MakeGenericType(typeof(int), typeof(string), typeof(long)); - - Assert.Collection( - frames, - frame => AssertFrame.Component(frame, genericComponentType.FullName, 4, 0), - frame => AssertFrame.Attribute(frame, "Item1", 3, 1), - frame => AssertFrame.Attribute(frame, "Item2", "FOO", 2), - frame => AssertFrame.Attribute(frame, "Item3", 39L, 3), - frame => AssertFrame.Text(frame, "3", 0), - frame => AssertFrame.Text(frame, "FOO", 1), - frame => AssertFrame.Text(frame, "39", 2)); - } - } -} diff --git a/src/Components/Blazor/Build/test/Microsoft.AspNetCore.Blazor.Build.Tests.csproj b/src/Components/Blazor/Build/test/Microsoft.AspNetCore.Blazor.Build.Tests.csproj deleted file mode 100644 index eee74d8755e6e0858e9c5bcfe4dbf7b7a823e291..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/Microsoft.AspNetCore.Blazor.Build.Tests.csproj +++ /dev/null @@ -1,54 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> - - <!-- Exclude the TestFiles directory from default wildcards --> - <DefaultItemExcludes>$(DefaultItemExcludes);TestFiles\**\*</DefaultItemExcludes> - <!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. --> - <CompileUsingReferenceAssemblies>false</CompileUsingReferenceAssemblies> - </PropertyGroup> - - <ItemGroup> - <!-- Embed test files so they can be referenced in tests --> - <EmbeddedResource Include="TestFiles\**" /> - </ItemGroup> - - <PropertyGroup Condition="'$(GenerateBaselines)'=='true'"> - <DefineConstants>GENERATE_BASELINES;$(DefineConstants)</DefineConstants> - </PropertyGroup> - - <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> - <DefineConstants>TRACE</DefineConstants> - </PropertyGroup> - - <ItemGroup> - <Reference Include="Microsoft.AspNetCore.Blazor.Build" /> - <Reference Include="Microsoft.AspNetCore.Blazor.Mono" /> - <Reference Include="Microsoft.AspNetCore.Mvc.Razor.Extensions" /> - <Reference Include="Microsoft.AspNetCore.Razor.Language" /> - <Reference Include="Microsoft.CodeAnalysis.Razor" /> - <!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. --> - <Reference Include="Microsoft.AspNetCore.Components" /> - </ItemGroup> - - <ItemGroup> - <ProjectReference Include="..\..\testassets\StandaloneApp\StandaloneApp.csproj" /> - <Compile Include="$(SharedSourceRoot)test\SkipOnHelixAttribute.cs" /> - - <Compile Include="$(ComponentsSharedSourceRoot)test\**\*.cs" LinkBase="Helpers" /> - </ItemGroup> - - <!-- A bit of msbuild magic to support reference resolver tests --> - <Target Name="CreateReferenceHintPathsList" AfterTargets="Build"> - <ItemGroup> - <_BclDirectory Include="$(MonoBaseClassLibraryPath)" /> - <_BclDirectory Include="$(MonoBaseClassLibraryFacadesPath)" /> - <_BclDirectory Include="$(MonoWasmFrameworkPath)" /> - </ItemGroup> - - <WriteLinesToFile Lines="@(ReferencePath)" File="$(TargetDir)referenceHints.txt" WriteOnlyWhenDifferent="true" Overwrite="true" /> - <WriteLinesToFile Lines="@(_BclDirectory)" File="$(TargetDir)bclLocations.txt" WriteOnlyWhenDifferent="true" Overwrite="true" /> - </Target> - -</Project> diff --git a/src/Components/Blazor/Build/test/Razor/NotFoundProjectItem.cs b/src/Components/Blazor/Build/test/Razor/NotFoundProjectItem.cs deleted file mode 100644 index ec5359db654b18de265f27ad57ccfe5c7d241f83..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/Razor/NotFoundProjectItem.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; - -namespace Microsoft.AspNetCore.Razor.Language -{ - /// <summary> - /// A <see cref="RazorProjectItem"/> that does not exist. - /// </summary> - internal class NotFoundProjectItem : RazorProjectItem - { - /// <summary> - /// Initializes a new instance of <see cref="NotFoundProjectItem"/>. - /// </summary> - /// <param name="basePath">The base path.</param> - /// <param name="path">The path.</param> - public NotFoundProjectItem(string basePath, string path) - { - BasePath = basePath; - FilePath = path; - } - - /// <inheritdoc /> - public override string BasePath { get; } - - /// <inheritdoc /> - public override string FilePath { get; } - - /// <inheritdoc /> - public override bool Exists => false; - - /// <inheritdoc /> - public override string PhysicalPath => throw new NotSupportedException(); - - /// <inheritdoc /> - public override Stream Read() => throw new NotSupportedException(); - } -} \ No newline at end of file diff --git a/src/Components/Blazor/Build/test/Razor/TestFile.cs b/src/Components/Blazor/Build/test/Razor/TestFile.cs deleted file mode 100644 index 70d5cacd70daef3137c4f2c10fa1ee5d0065084b..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/Razor/TestFile.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Reflection; -using Xunit; - -namespace Microsoft.AspNetCore.Razor.Language -{ - public class TestFile - { - private TestFile(string resourceName, Assembly assembly) - { - Assembly = assembly; - ResourceName = Assembly.GetName().Name + "." + resourceName.Replace('/', '.').Replace('\\', '.'); - } - - public Assembly Assembly { get; } - - public string ResourceName { get; } - - public static TestFile Create(string resourceName, Type type) - { - return new TestFile(resourceName, type.GetTypeInfo().Assembly); - } - - public static TestFile Create(string resourceName, Assembly assembly) - { - return new TestFile(resourceName, assembly); - } - - public Stream OpenRead() - { - var stream = Assembly.GetManifestResourceStream(ResourceName); - if (stream == null) - { - Assert.True(false, string.Format("Manifest resource: {0} not found", ResourceName)); - } - - return stream; - } - - public bool Exists() - { - var resourceNames = Assembly.GetManifestResourceNames(); - foreach (var resourceName in resourceNames) - { - // Resource names are case-sensitive. - if (string.Equals(ResourceName, resourceName, StringComparison.Ordinal)) - { - return true; - } - } - - return false; - } - - public string ReadAllText() - { - using (var reader = new StreamReader(OpenRead())) - { - // The .Replace() calls normalize line endings, in case you get \n instead of \r\n - // since all the unit tests rely on the assumption that the files will have \r\n endings. - return reader.ReadToEnd().Replace("\r", "").Replace("\n", "\r\n"); - } - } - - /// <summary> - /// Saves the file to the specified path. - /// </summary> - public void Save(string filePath) - { - var directory = Path.GetDirectoryName(filePath); - if (!Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - - using (var outStream = File.Create(filePath)) - { - using (var inStream = OpenRead()) - { - inStream.CopyTo(outStream); - } - } - } - } -} diff --git a/src/Components/Blazor/Build/test/Razor/TestProject.cs b/src/Components/Blazor/Build/test/Razor/TestProject.cs deleted file mode 100644 index 5f14ef4bedb39fb6042d31b456431e52d0ac4cae..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/Razor/TestProject.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; - -namespace Microsoft.AspNetCore.Razor.Language -{ - public static class TestProject - { - public static string GetProjectDirectory(Type type) - { - var solutionDir = GetSolutionRootDirectory("Components"); - - var assemblyName = type.Assembly.GetName().Name; - - var projectDirectory = Path.Combine(solutionDir, "test", assemblyName); - if (!Directory.Exists(projectDirectory)) - { - throw new InvalidOperationException( -$@"Could not locate project directory for type {type.FullName}. -Directory probe path: {projectDirectory}."); - } - - return projectDirectory; - } - - public static string GetSolutionRootDirectory(string solution) - { - var applicationBasePath = AppContext.BaseDirectory; - var directoryInfo = new DirectoryInfo(applicationBasePath); - - do - { - var projectFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, $"{solution}.sln")); - if (projectFileInfo.Exists) - { - return projectFileInfo.DirectoryName; - } - - directoryInfo = directoryInfo.Parent; - } - while (directoryInfo.Parent != null); - - throw new Exception($"Solution file {solution}.sln could not be found in {applicationBasePath} or its parent directories."); - } - } -} diff --git a/src/Components/Blazor/Build/test/Razor/VirtualProjectFileSystem.cs b/src/Components/Blazor/Build/test/Razor/VirtualProjectFileSystem.cs deleted file mode 100644 index 3d694a82d022b8a21ef4bbabb0c9b782222e0c92..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/Razor/VirtualProjectFileSystem.cs +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; - -namespace Microsoft.AspNetCore.Razor.Language -{ - internal class VirtualRazorProjectFileSystem : RazorProjectFileSystem - { - private readonly DirectoryNode _root = new DirectoryNode("/"); - - public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath) - { - basePath = NormalizeAndEnsureValidPath(basePath); - var directory = _root.GetDirectory(basePath); - return directory?.EnumerateItems() ?? Enumerable.Empty<RazorProjectItem>(); - } - - [Obsolete("Use GetItem(string path, string fileKind)] instead")] - public override RazorProjectItem GetItem(string path) - { - return GetItem(path, fileKind: null); - } - - public override RazorProjectItem GetItem(string path, string fileKind) - { - // We ignore fileKind here because the _root is pre-filled with project items that already have fileKinds defined. This is - // a unique circumstance where the RazorProjectFileSystem is actually pre-filled with all of its project items on construction. - - path = NormalizeAndEnsureValidPath(path); - return _root.GetItem(path) ?? new NotFoundProjectItem(string.Empty, path); - } - - public void Add(RazorProjectItem projectItem) - { - if (projectItem == null) - { - throw new ArgumentNullException(nameof(projectItem)); - } - - var filePath = NormalizeAndEnsureValidPath(projectItem.FilePath); - _root.AddFile(new FileNode(filePath, projectItem)); - } - - // Internal for testing - [DebuggerDisplay("{Path}")] - internal class DirectoryNode - { - public DirectoryNode(string path) - { - Path = path; - } - - public string Path { get; } - - public List<DirectoryNode> Directories { get; } = new List<DirectoryNode>(); - - public List<FileNode> Files { get; } = new List<FileNode>(); - - public void AddFile(FileNode fileNode) - { - var filePath = fileNode.Path; - if (!filePath.StartsWith(Path, StringComparison.OrdinalIgnoreCase)) - { - var message = "Error"; - throw new InvalidOperationException(message); - } - - // Look for the first / that appears in the path after the current directory path. - var directoryPath = GetDirectoryPath(filePath); - var directory = GetOrAddDirectory(this, directoryPath, createIfNotExists: true); - Debug.Assert(directory != null); - directory.Files.Add(fileNode); - } - - public DirectoryNode GetDirectory(string path) - { - if (!path.StartsWith(Path, StringComparison.OrdinalIgnoreCase)) - { - var message = "Error"; - throw new InvalidOperationException(message); - } - - return GetOrAddDirectory(this, path); - } - - public IEnumerable<RazorProjectItem> EnumerateItems() - { - foreach (var file in Files) - { - yield return file.ProjectItem; - } - - foreach (var directory in Directories) - { - foreach (var file in directory.EnumerateItems()) - { - yield return file; - } - } - } - - public RazorProjectItem GetItem(string path) - { - if (!path.StartsWith(Path, StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidOperationException("Error"); - } - - var directoryPath = GetDirectoryPath(path); - var directory = GetOrAddDirectory(this, directoryPath); - if (directory == null) - { - return null; - } - - foreach (var file in directory.Files) - { - var filePath = file.Path; - var directoryLength = directory.Path.Length; - - // path, filePath -> /Views/Home/Index.cshtml - // directory.Path -> /Views/Home/ - // We only need to match the file name portion since we've already matched the directory segment. - if (string.Compare(path, directoryLength, filePath, directoryLength, path.Length - directoryLength, StringComparison.OrdinalIgnoreCase) == 0) - { - return file.ProjectItem; - } - } - - return null; - } - - private static string GetDirectoryPath(string path) - { - // /dir1/dir2/file.cshtml -> /dir1/dir2/ - var fileNameIndex = path.LastIndexOf('/'); - if (fileNameIndex == -1) - { - return path; - } - - return path.Substring(0, fileNameIndex + 1); - } - - private static DirectoryNode GetOrAddDirectory( - DirectoryNode directory, - string path, - bool createIfNotExists = false) - { - Debug.Assert(!string.IsNullOrEmpty(path)); - if (path[path.Length - 1] != '/') - { - path += '/'; - } - - int index; - while ((index = path.IndexOf('/', directory.Path.Length)) != -1 && index != path.Length) - { - var subDirectory = FindSubDirectory(directory, path); - - if (subDirectory == null) - { - if (createIfNotExists) - { - var directoryPath = path.Substring(0, index + 1); // + 1 to include trailing slash - subDirectory = new DirectoryNode(directoryPath); - directory.Directories.Add(subDirectory); - } - else - { - return null; - } - } - - directory = subDirectory; - } - - return directory; - } - - private static DirectoryNode FindSubDirectory(DirectoryNode parentDirectory, string path) - { - for (var i = 0; i < parentDirectory.Directories.Count; i++) - { - // ParentDirectory.Path -> /Views/Home/ - // CurrentDirectory.Path -> /Views/Home/SubDir/ - // Path -> /Views/Home/SubDir/MorePath/File.cshtml - // Each invocation of FindSubDirectory returns the immediate subdirectory along the path to the file. - - var currentDirectory = parentDirectory.Directories[i]; - var directoryPath = currentDirectory.Path; - var startIndex = parentDirectory.Path.Length; - var directoryNameLength = directoryPath.Length - startIndex; - - if (string.Compare(path, startIndex, directoryPath, startIndex, directoryPath.Length - startIndex, StringComparison.OrdinalIgnoreCase) == 0) - { - return currentDirectory; - } - } - - return null; - } - } - - // Internal for testing - [DebuggerDisplay("{Path}")] - internal struct FileNode - { - public FileNode(string path, RazorProjectItem projectItem) - { - Path = path; - ProjectItem = projectItem; - } - - public string Path { get; } - - public RazorProjectItem ProjectItem { get; } - } - } -} diff --git a/src/Components/Blazor/Build/test/Razor/VirtualProjectItem.cs b/src/Components/Blazor/Build/test/Razor/VirtualProjectItem.cs deleted file mode 100644 index 68c135d7151084056c9a6a8ecc72b567a5974256..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/Razor/VirtualProjectItem.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.IO; - -namespace Microsoft.AspNetCore.Razor.Language -{ - internal class VirtualProjectItem : RazorProjectItem - { - private readonly byte[] _content; - - public VirtualProjectItem( - string basePath, - string filePath, - string physicalPath, - string relativePhysicalPath, - string fileKind, - byte[] content) - { - BasePath = basePath; - FilePath = filePath; - PhysicalPath = physicalPath; - RelativePhysicalPath = relativePhysicalPath; - _content = content; - - // Base class will detect based on file-extension. - FileKind = fileKind ?? base.FileKind; - } - - public override string BasePath { get; } - - public override string RelativePhysicalPath { get; } - - public override string FileKind { get; } - - public override string FilePath { get; } - - public override string PhysicalPath { get; } - - public override bool Exists => true; - - public override Stream Read() - { - return new MemoryStream(_content); - } - } -} diff --git a/src/Components/Blazor/Build/test/RazorIntegrationTestBase.cs b/src/Components/Blazor/Build/test/RazorIntegrationTestBase.cs deleted file mode 100644 index 943a658a36f81f52e16ebfdfd8b1e51b34e9605d..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/RazorIntegrationTestBase.cs +++ /dev/null @@ -1,542 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.ExceptionServices; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Rendering; -using Microsoft.AspNetCore.Components.RenderTree; -using Microsoft.AspNetCore.Components.Test.Helpers; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.AspNetCore.Razor.Language.CodeGeneration; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Razor; -using Microsoft.Extensions.Logging.Abstractions; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Microsoft.AspNetCore.Blazor.Build.Test -{ - public class RazorIntegrationTestBase - { - private static readonly AsyncLocal<ITestOutputHelper> _output = new AsyncLocal<ITestOutputHelper>(); - - internal const string ArbitraryWindowsPath = "x:\\dir\\subdir\\Test"; - internal const string ArbitraryMacLinuxPath = "/dir/subdir/Test"; - - // Creating the initial compilation + reading references is on the order of 250ms without caching - // so making sure it doesn't happen for each test. - private static readonly CSharpCompilation BaseCompilation; - - private static CSharpParseOptions CSharpParseOptions { get; } - - static RazorIntegrationTestBase() - { - var referenceAssemblyRoots = new[] - { - typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly, // System.Runtime - typeof(ComponentBase).Assembly, - typeof(RazorIntegrationTestBase).Assembly, // Reference this assembly, so that we can refer to test component types - }; - - var referenceAssemblies = referenceAssemblyRoots - .SelectMany(assembly => assembly.GetReferencedAssemblies().Concat(new[] { assembly.GetName() })) - .Distinct() - .Select(Assembly.Load) - .Select(assembly => MetadataReference.CreateFromFile(assembly.Location)) - .ToList(); - BaseCompilation = CSharpCompilation.Create( - "TestAssembly", - Array.Empty<SyntaxTree>(), - referenceAssemblies, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); - - CSharpParseOptions = new CSharpParseOptions(LanguageVersion.Preview); - } - - public RazorIntegrationTestBase(ITestOutputHelper output) - { - _output.Value = output; - - AdditionalSyntaxTrees = new List<SyntaxTree>(); - AdditionalRazorItems = new List<RazorProjectItem>(); - - Configuration = RazorConfiguration.Create(RazorLanguageVersion.Latest, "MVC-3.0", Array.Empty<RazorExtension>()); - FileKind = FileKinds.Component; // Treat input files as components by default. - FileSystem = new VirtualRazorProjectFileSystem(); - PathSeparator = Path.DirectorySeparatorChar.ToString(); - WorkingDirectory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ArbitraryWindowsPath : ArbitraryMacLinuxPath; - - // Many of the rendering tests include line endings in the output. - LineEnding = "\n"; - NormalizeSourceLineEndings = true; - - DefaultRootNamespace = "Test"; // Matches the default working directory - DefaultFileName = "TestComponent.cshtml"; - } - - internal List<RazorProjectItem> AdditionalRazorItems { get; } - - internal List<SyntaxTree> AdditionalSyntaxTrees { get; } - - internal virtual RazorConfiguration Configuration { get; } - - internal virtual string DefaultRootNamespace { get; } - - internal virtual string DefaultFileName { get; } - - internal virtual bool DesignTime { get; } - - internal virtual string FileKind { get; } - - internal virtual VirtualRazorProjectFileSystem FileSystem { get; } - - // Used to force a specific style of line-endings for testing. This matters - // for the baseline tests that exercise line mappings. Even though we normalize - // newlines for testing, the difference between platforms affects the data through - // the *count* of characters written. - internal virtual string LineEnding { get; } - - internal virtual string PathSeparator { get; } - - internal virtual bool NormalizeSourceLineEndings { get; } - - internal virtual bool UseTwoPhaseCompilation { get; } - - internal virtual string WorkingDirectory { get; } - - // Intentionally private, we don't want tests messing with this because it's fragile. - private RazorProjectEngine CreateProjectEngine(MetadataReference[] references) - { - return RazorProjectEngine.Create(Configuration, FileSystem, b => - { - b.SetRootNamespace(DefaultRootNamespace); - - // Turn off checksums, we're testing code generation. - b.Features.Add(new SuppressChecksum()); - - if (LineEnding != null) - { - b.Phases.Insert(0, new ForceLineEndingPhase(LineEnding)); - } - - // Including MVC here so that we can find any issues that arise from mixed MVC + Components. - Microsoft.AspNetCore.Mvc.Razor.Extensions.RazorExtensions.Register(b); - - // Features that use Roslyn are mandatory for components - Microsoft.CodeAnalysis.Razor.CompilerFeatures.Register(b); - - b.Features.Add(new CompilationTagHelperFeature()); - b.Features.Add(new DefaultMetadataReferenceFeature() - { - References = references, - }); - }); - } - - internal RazorProjectItem CreateProjectItem(string cshtmlRelativePath, string cshtmlContent) - { - var fullPath = WorkingDirectory + PathSeparator + cshtmlRelativePath; - - // FilePaths in Razor are **always** are of the form '/a/b/c.cshtml' - var filePath = cshtmlRelativePath.Replace('\\', '/'); - if (!filePath.StartsWith('/')) - { - filePath = '/' + filePath; - } - - if (NormalizeSourceLineEndings) - { - cshtmlContent = cshtmlContent.Replace("\r", "").Replace("\n", LineEnding); - } - - return new VirtualProjectItem( - WorkingDirectory, - filePath, - fullPath, - cshtmlRelativePath, - FileKind, - Encoding.UTF8.GetBytes(cshtmlContent.TrimStart())); - } - - protected CompileToCSharpResult CompileToCSharp(string cshtmlContent) - { - return CompileToCSharp(DefaultFileName, cshtmlContent); - } - - protected CompileToCSharpResult CompileToCSharp(string cshtmlRelativePath, string cshtmlContent) - { - if (UseTwoPhaseCompilation) - { - // The first phase won't include any metadata references for component discovery. This mirrors - // what the build does. - var projectEngine = CreateProjectEngine(Array.Empty<MetadataReference>()); - - RazorCodeDocument codeDocument; - foreach (var item in AdditionalRazorItems) - { - // Result of generating declarations - codeDocument = projectEngine.ProcessDeclarationOnly(item); - Assert.Empty(codeDocument.GetCSharpDocument().Diagnostics); - - var syntaxTree = Parse(codeDocument.GetCSharpDocument().GeneratedCode, path: item.FilePath); - AdditionalSyntaxTrees.Add(syntaxTree); - } - - // Result of generating declarations - var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent); - codeDocument = projectEngine.ProcessDeclarationOnly(projectItem); - var declaration = new CompileToCSharpResult - { - BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees), - CodeDocument = codeDocument, - Code = codeDocument.GetCSharpDocument().GeneratedCode, - Diagnostics = codeDocument.GetCSharpDocument().Diagnostics, - }; - - // Result of doing 'temp' compilation - var tempAssembly = CompileToAssembly(declaration); - - // Add the 'temp' compilation as a metadata reference - var references = BaseCompilation.References.Concat(new[] { tempAssembly.Compilation.ToMetadataReference() }).ToArray(); - projectEngine = CreateProjectEngine(references); - - // Now update the any additional files - foreach (var item in AdditionalRazorItems) - { - // Result of generating declarations - codeDocument = DesignTime ? projectEngine.ProcessDesignTime(item) : projectEngine.Process(item); - Assert.Empty(codeDocument.GetCSharpDocument().Diagnostics); - - // Replace the 'declaration' syntax tree - var syntaxTree = Parse(codeDocument.GetCSharpDocument().GeneratedCode, path: item.FilePath); - AdditionalSyntaxTrees.RemoveAll(st => st.FilePath == item.FilePath); - AdditionalSyntaxTrees.Add(syntaxTree); - } - - // Result of real code generation for the document under test - codeDocument = DesignTime ? projectEngine.ProcessDesignTime(projectItem) : projectEngine.Process(projectItem); - - _output.Value.WriteLine("Use this output when opening an issue"); - _output.Value.WriteLine(string.Empty); - - _output.Value.WriteLine($"## Main source file ({projectItem.FileKind}):"); - _output.Value.WriteLine("```"); - _output.Value.WriteLine(ReadProjectItem(projectItem)); - _output.Value.WriteLine("```"); - _output.Value.WriteLine(string.Empty); - - foreach (var item in AdditionalRazorItems) - { - _output.Value.WriteLine($"### Additional source file ({item.FileKind}):"); - _output.Value.WriteLine("```"); - _output.Value.WriteLine(ReadProjectItem(item)); - _output.Value.WriteLine("```"); - _output.Value.WriteLine(string.Empty); - } - - _output.Value.WriteLine("## Generated C#:"); - _output.Value.WriteLine("```C#"); - _output.Value.WriteLine(codeDocument.GetCSharpDocument().GeneratedCode); - _output.Value.WriteLine("```"); - - return new CompileToCSharpResult - { - BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees), - CodeDocument = codeDocument, - Code = codeDocument.GetCSharpDocument().GeneratedCode, - Diagnostics = codeDocument.GetCSharpDocument().Diagnostics, - }; - } - else - { - // For single phase compilation tests just use the base compilation's references. - // This will include the built-in Blazor components. - var projectEngine = CreateProjectEngine(BaseCompilation.References.ToArray()); - - var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent); - var codeDocument = DesignTime ? projectEngine.ProcessDesignTime(projectItem) : projectEngine.Process(projectItem); - - // Log the generated code for test results. - _output.Value.WriteLine("Use this output when opening an issue"); - _output.Value.WriteLine(string.Empty); - - _output.Value.WriteLine($"## Main source file ({projectItem.FileKind}):"); - _output.Value.WriteLine("```"); - _output.Value.WriteLine(ReadProjectItem(projectItem)); - _output.Value.WriteLine("```"); - _output.Value.WriteLine(string.Empty); - - _output.Value.WriteLine("## Generated C#:"); - _output.Value.WriteLine("```C#"); - _output.Value.WriteLine(codeDocument.GetCSharpDocument().GeneratedCode); - _output.Value.WriteLine("```"); - - return new CompileToCSharpResult - { - BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees), - CodeDocument = codeDocument, - Code = codeDocument.GetCSharpDocument().GeneratedCode, - Diagnostics = codeDocument.GetCSharpDocument().Diagnostics, - }; - } - } - - protected CompileToAssemblyResult CompileToAssembly(string cshtmlRelativePath, string cshtmlContent) - { - var cSharpResult = CompileToCSharp(cshtmlRelativePath, cshtmlContent); - return CompileToAssembly(cSharpResult); - } - - protected CompileToAssemblyResult CompileToAssembly(CompileToCSharpResult cSharpResult, bool throwOnFailure = true) - { - if (cSharpResult.Diagnostics.Any()) - { - var diagnosticsLog = string.Join(Environment.NewLine, cSharpResult.Diagnostics.Select(d => d.ToString()).ToArray()); - throw new InvalidOperationException($"Aborting compilation to assembly because RazorCompiler returned nonempty diagnostics: {diagnosticsLog}"); - } - - var syntaxTrees = new[] - { - Parse(cSharpResult.Code), - }; - - var compilation = cSharpResult.BaseCompilation.AddSyntaxTrees(syntaxTrees); - - var diagnostics = compilation - .GetDiagnostics() - .Where(d => d.Severity != DiagnosticSeverity.Hidden); - - if (diagnostics.Any() && throwOnFailure) - { - throw new CompilationFailedException(compilation); - } - else if (diagnostics.Any()) - { - return new CompileToAssemblyResult - { - Compilation = compilation, - Diagnostics = diagnostics, - }; - } - - using (var peStream = new MemoryStream()) - { - compilation.Emit(peStream); - - return new CompileToAssemblyResult - { - Compilation = compilation, - Diagnostics = diagnostics, - Assembly = diagnostics.Any() ? null : Assembly.Load(peStream.ToArray()) - }; - } - } - - protected IComponent CompileToComponent(string cshtmlSource) - { - var assemblyResult = CompileToAssembly(DefaultFileName, cshtmlSource); - - var componentFullTypeName = $"{DefaultRootNamespace}.{Path.GetFileNameWithoutExtension(DefaultFileName)}"; - return CompileToComponent(assemblyResult, componentFullTypeName); - } - - protected IComponent CompileToComponent(CompileToCSharpResult cSharpResult, string fullTypeName) - { - return CompileToComponent(CompileToAssembly(cSharpResult), fullTypeName); - } - - protected IComponent CompileToComponent(CompileToAssemblyResult assemblyResult, string fullTypeName) - { - var componentType = assemblyResult.Assembly.GetType(fullTypeName); - if (componentType == null) - { - throw new XunitException( - $"Failed to find component type '{fullTypeName}'. Found types:" + Environment.NewLine + - string.Join(Environment.NewLine, assemblyResult.Assembly.ExportedTypes.Select(t => t.FullName))); - } - - return (IComponent)Activator.CreateInstance(componentType); - } - - protected static CSharpSyntaxTree Parse(string text, string path = null) - { - return (CSharpSyntaxTree)CSharpSyntaxTree.ParseText(text, CSharpParseOptions, path: path); - } - - protected static string FullTypeName<T>() => typeof(T).FullName.Replace('+', '.'); - - protected RenderTreeFrame[] GetRenderTree(IComponent component) - { - var renderer = new TestRenderer(); - return GetRenderTree(renderer, component); - } - - protected private RenderTreeFrame[] GetRenderTree(TestRenderer renderer, IComponent component) - { - renderer.AttachComponent(component); - var task = renderer.Dispatcher.InvokeAsync(() => component.SetParametersAsync(ParameterView.Empty)); - // we will have to change this method if we add a test that does actual async work. - Assert.True(task.Status.HasFlag(TaskStatus.RanToCompletion) || task.Status.HasFlag(TaskStatus.Faulted)); - if (task.IsFaulted) - { - ExceptionDispatchInfo.Capture(task.Exception.InnerException).Throw(); - } - return renderer.LatestBatchReferenceFrames; - } - - protected ArrayRange<RenderTreeFrame> GetFrames(RenderFragment fragment) - { - var builder = new RenderTreeBuilder(); - fragment(builder); - return builder.GetFrames(); - } - - protected static void AssertSourceEquals(string expected, CompileToCSharpResult generated) - { - // Normalize the paths inside the expected result to match the OS paths - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - var windowsPath = Path.Combine(ArbitraryWindowsPath, generated.CodeDocument.Source.RelativePath).Replace('/', '\\'); - expected = expected.Replace(windowsPath, generated.CodeDocument.Source.FilePath); - } - - expected = expected.Trim(); - Assert.Equal(expected, generated.Code.Trim(), ignoreLineEndingDifferences: true); - } - - private static string ReadProjectItem(RazorProjectItem item) - { - using (var reader = new StreamReader(item.Read())) - { - return reader.ReadToEnd(); - } - } - - protected class CompileToCSharpResult - { - // A compilation that can be used *with* this code to compile an assembly - public Compilation BaseCompilation { get; set; } - public RazorCodeDocument CodeDocument { get; set; } - public string Code { get; set; } - public IEnumerable<RazorDiagnostic> Diagnostics { get; set; } - } - - protected class CompileToAssemblyResult - { - public Assembly Assembly { get; set; } - public Compilation Compilation { get; set; } - public string VerboseLog { get; set; } - public IEnumerable<Diagnostic> Diagnostics { get; set; } - } - - protected class TestRenderer : Renderer - { - public TestRenderer() : base(new TestServiceProvider(), NullLoggerFactory.Instance) - { - } - - public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); - - public RenderTreeFrame[] LatestBatchReferenceFrames { get; private set; } - - public void AttachComponent(IComponent component) - => AssignRootComponentId(component); - - protected override void HandleException(Exception exception) - { - ExceptionDispatchInfo.Capture(exception).Throw(); - } - - protected override Task UpdateDisplayAsync(in RenderBatch renderBatch) - { - LatestBatchReferenceFrames = renderBatch.ReferenceFrames.AsEnumerable().ToArray(); - return Task.CompletedTask; - } - } - - private class CompilationFailedException : XunitException - { - public CompilationFailedException(Compilation compilation) - { - Compilation = compilation; - } - - public Compilation Compilation { get; } - - public override string Message - { - get - { - var builder = new StringBuilder(); - builder.AppendLine("Compilation failed: "); - - var diagnostics = Compilation.GetDiagnostics(); - var syntaxTreesWithErrors = new HashSet<SyntaxTree>(); - foreach (var diagnostic in diagnostics) - { - builder.AppendLine(diagnostic.ToString()); - - if (diagnostic.Location.IsInSource) - { - syntaxTreesWithErrors.Add(diagnostic.Location.SourceTree); - } - } - - if (syntaxTreesWithErrors.Any()) - { - builder.AppendLine(); - builder.AppendLine(); - - foreach (var syntaxTree in syntaxTreesWithErrors) - { - builder.AppendLine($"File {syntaxTree.FilePath ?? "unknown"}:"); - builder.AppendLine(syntaxTree.GetText().ToString()); - } - } - - return builder.ToString(); - } - } - } - - private class SuppressChecksum : IConfigureRazorCodeGenerationOptionsFeature - { - public int Order => 0; - - public RazorEngine Engine { get; set; } - - public void Configure(RazorCodeGenerationOptionsBuilder options) - { - options.SuppressChecksum = true; - } - } - - private class ForceLineEndingPhase : RazorEnginePhaseBase - { - public ForceLineEndingPhase(string lineEnding) - { - LineEnding = lineEnding; - } - - public string LineEnding { get; } - - protected override void ExecuteCore(RazorCodeDocument codeDocument) - { - var field = typeof(CodeRenderingContext).GetField("NewLineString", BindingFlags.Static | BindingFlags.NonPublic); - var key = field.GetValue(null); - codeDocument.Items[key] = LineEnding; - } - } - } -} diff --git a/src/Components/Blazor/Build/test/RenderingRazorIntegrationTest.cs b/src/Components/Blazor/Build/test/RenderingRazorIntegrationTest.cs deleted file mode 100644 index 3f380cbc7966fe59abf1bfab68001b7748bbcf40..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/test/RenderingRazorIntegrationTest.cs +++ /dev/null @@ -1,744 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.RenderTree; -using Microsoft.AspNetCore.Components.Test.Helpers; -using Microsoft.AspNetCore.Components.Web; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.AspNetCore.Blazor.Build.Test -{ - // Integration tests for the end-to-end of successful Razor compilation of component definitions - // Includes running the component code to verify the output. - public class RenderingRazorIntegrationTest : RazorIntegrationTestBase - { - public RenderingRazorIntegrationTest(ITestOutputHelper output) - : base(output) - { - } - - [Fact] - public void SupportsPlainText() - { - // Arrange/Act - var component = CompileToComponent("Some plain text"); - var frames = GetRenderTree(component); - - // Assert - Assert.Collection(frames, - frame => AssertFrame.Text(frame, "Some plain text", 0)); - } - - [Fact] - public void SupportsCSharpExpressions() - { - // Arrange/Act - var component = CompileToComponent(@" - @(""Hello"") - @((object)null) - @(123) - @(new object()) - "); - - // Assert - var frames = GetRenderTree(component); - Assert.Collection(frames, - frame => AssertFrame.Text(frame, "Hello", 0), - frame => AssertFrame.MarkupWhitespace(frame, 1), - frame => AssertFrame.TextWhitespace(frame, 2), // @((object)null) - frame => AssertFrame.MarkupWhitespace(frame, 3), - frame => AssertFrame.Text(frame, "123", 4), - frame => AssertFrame.MarkupWhitespace(frame, 5), - frame => AssertFrame.Text(frame, new object().ToString(), 6)); - } - - [Fact] - public void SupportsCSharpFunctionsBlock() - { - // Arrange/Act - var component = CompileToComponent(@" - @foreach(var item in items) { - @item - } - @code { - string[] items = new[] { ""First"", ""Second"", ""Third"" }; - } - "); - - // Assert - var frames = GetRenderTree(component); - Assert.Collection(frames, - frame => AssertFrame.Text(frame, "First", 0), - frame => AssertFrame.Text(frame, "Second", 0), - frame => AssertFrame.Text(frame, "Third", 0)); - } - - [Fact] - public void SupportsElementsWithDynamicContent() - { - // Arrange/Act - var component = CompileToComponent("<myelem>Hello @(\"there\")</myelem>"); - - // Assert - Assert.Collection(GetRenderTree(component), - frame => AssertFrame.Element(frame, "myelem", 3, 0), - frame => AssertFrame.Text(frame, "Hello ", 1), - frame => AssertFrame.Text(frame, "there", 2)); - } - - [Fact] - public void SupportsElementsAsStaticBlock() - { - // Arrange/Act - var component = CompileToComponent("<myelem>Hello</myelem>"); - - // Assert - Assert.Collection(GetRenderTree(component), - frame => AssertFrame.Markup(frame, "<myelem>Hello</myelem>", 0)); - } - - [Fact] - public void CreatesSeparateMarkupFrameForEachTopLevelStaticElement() - { - // The JavaScript-side rendering code does not rely on this behavior. It supports - // inserting markup frames with arbitrary markup (e.g., multiple top-level elements - // or none). This test exists only as an observation of the current behavior rather - // than a promise that we never want to change it. - - // Arrange/Act - var component = CompileToComponent( - "<root>@(\"Hi\") <child1>a</child1> <child2><another>b</another></child2> </root>"); - - // Assert - var frames = GetRenderTree(component); - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "root", 5, 0), - frame => AssertFrame.Text(frame, "Hi", 1), - frame => AssertFrame.Text(frame, " ", 2), - frame => AssertFrame.Markup(frame, "<child1>a</child1> ", 3), - frame => AssertFrame.Markup(frame, "<child2><another>b</another></child2> ", 4)); - } - - [Fact] - public void RendersMarkupStringAsMarkupFrame() - { - // Arrange/Act - var component = CompileToComponent( - "@{ var someMarkup = new MarkupString(\"<div>Hello</div>\"); }" - + "<p>@someMarkup</p>"); - - // Assert - Assert.Collection(GetRenderTree(component), - frame => AssertFrame.Element(frame, "p", 2, 0), - frame => AssertFrame.Markup(frame, "<div>Hello</div>", 1)); - } - - [Fact] - public void SupportsSelfClosingElementsWithDynamicContent() - { - // Arrange/Act - var component = CompileToComponent("Some text so elem isn't at position 0 <myelem myattr=@(\"val\") />"); - - // Assert - Assert.Collection(GetRenderTree(component), - frame => AssertFrame.Text(frame, "Some text so elem isn't at position 0 ", 0), - frame => AssertFrame.Element(frame, "myelem", 2, 1), - frame => AssertFrame.Attribute(frame, "myattr", "val", 2)); - } - - [Fact] - public void SupportsSelfClosingElementsAsStaticBlock() - { - // Arrange/Act - var component = CompileToComponent("Some text so elem isn't at position 0 <input attr='123' />"); - - // Assert - Assert.Collection( - GetRenderTree(component), - frame => AssertFrame.Markup(frame, "Some text so elem isn't at position 0 <input attr=\"123\">", 0)); - } - - [Fact] - public void SupportsVoidHtmlElements() - { - // Arrange/Act - var component = CompileToComponent("Some text so elem isn't at position 0 <img>"); - - // Assert - Assert.Collection( - GetRenderTree(component), - frame => AssertFrame.Markup(frame, "Some text so elem isn't at position 0 <img>", 0)); - } - - [Fact] - public void SupportsComments() - { - // Arrange/Act - var component = CompileToComponent("Start<!-- My comment -->End"); - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Markup(frame, "StartEnd", 0)); - } - - [Fact] - public void SupportsAttributesWithLiteralValues() - { - // Arrange/Act - var component = CompileToComponent("<elem attrib-one=\"Value 1\" a2='v2'>@(\"Hello\")</elem>"); - - // Assert - Assert.Collection(GetRenderTree(component), - frame => AssertFrame.Element(frame, "elem", 4, 0), - frame => AssertFrame.Attribute(frame, "attrib-one", "Value 1", 1), - frame => AssertFrame.Attribute(frame, "a2", "v2", 2), - frame => AssertFrame.Text(frame, "Hello", 3)); - } - - [Fact] - public void SupportsAttributesWithStringExpressionValues() - { - // Arrange/Act - var component = CompileToComponent( - "@{ var myValue = \"My string\"; }" - + "<elem attr=@myValue />"); - - // Assert - Assert.Collection(GetRenderTree(component), - frame => AssertFrame.Element(frame, "elem", 2, 0), - frame => AssertFrame.Attribute(frame, "attr", "My string", 1)); - } - - [Fact] - public void SupportsAttributesWithNonStringExpressionValues() - { - // Arrange/Act - var component = CompileToComponent( - "@{ var myValue = 123; }" - + "<elem attr=@myValue />"); - - // Assert - Assert.Collection(GetRenderTree(component), - frame => AssertFrame.Element(frame, "elem", 2, 0), - frame => AssertFrame.Attribute(frame, "attr", "123", 1)); - } - - [Fact] - public void SupportsAttributesWithInterpolatedStringExpressionValues() - { - // Arrange/Act - var component = CompileToComponent( - "@{ var myValue = \"world\"; var myNum=123; }" - + "<elem attr=\"Hello, @myValue.ToUpperInvariant() with number @(myNum*2)!\" />"); - - // Assert - Assert.Collection(GetRenderTree(component), - frame => AssertFrame.Element(frame, "elem", 2, 0), - frame => AssertFrame.Attribute(frame, "attr", "Hello, WORLD with number 246!", 1)); - } - - [Fact] - public void SupportsAttributesWithInterpolatedTernaryExpressionValues() - { - // Arrange/Act - var component = CompileToComponent( - "@{ var myValue = \"world\"; }" - + "<elem attr=\"Hello, @(true ? myValue : \"nothing\")!\" />"); - - // Assert - Assert.Collection(GetRenderTree(component), - frame => AssertFrame.Element(frame, "elem", 2, 0), - frame => AssertFrame.Attribute(frame, "attr", "Hello, world!", 1)); - } - - [Fact] - public void SupportsHyphenedAttributesWithCSharpExpressionValues() - { - // Arrange/Act - var component = CompileToComponent( - "@{ var myValue = \"My string\"; }" - + "<elem abc-def=@myValue />"); - - // Assert - Assert.Collection(GetRenderTree(component), - frame => AssertFrame.Element(frame, "elem", 2, 0), - frame => AssertFrame.Attribute(frame, "abc-def", "My string", 1)); - } - - [Fact] - public void SupportsDataDashAttributes() - { - // Arrange/Act - var component = CompileToComponent(@" -@{ - var myValue = ""Expression value""; -} -<elem data-abc=""Literal value"" data-def=""@myValue"" />"); - - // Assert - Assert.Collection( - GetRenderTree(component), - frame => AssertFrame.Element(frame, "elem", 3, 0), - frame => AssertFrame.Attribute(frame, "data-abc", "Literal value", 1), - frame => AssertFrame.Attribute(frame, "data-def", "Expression value", 2)); - } - - [Fact] - public void SupportsUsingStatements() - { - // Arrange/Act - var component = CompileToComponent( - @"@using System.Collections.Generic - @(typeof(List<string>).FullName)"); - var frames = GetRenderTree(component); - - // Assert - Assert.Collection(frames, - frame => AssertFrame.Text(frame, typeof(List<string>).FullName, 0)); - } - - [Fact] - public async Task SupportsTwoWayBindingForTextboxes() - { - // Arrange/Act - var component = CompileToComponent(@" -@using Microsoft.AspNetCore.Components.Web -<input @bind=""MyValue"" /> -@code { - public string MyValue { get; set; } = ""Initial value""; -}"); - var myValueProperty = component.GetType().GetProperty("MyValue"); - - var renderer = new TestRenderer(); - - // Assert - EventCallback setter = default; - var frames = GetRenderTree(renderer, component); - Assert.Collection(frames, - frame => AssertFrame.Element(frame, "input", 3, 0), - frame => AssertFrame.Attribute(frame, "value", "Initial value", 1), - frame => - { - AssertFrame.Attribute(frame, "onchange", 2); - setter = Assert.IsType<EventCallback>(frame.AttributeValue); - }); - - // Trigger the change event to show it updates the property - // - // This should always complete synchronously. - var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = "Modified value", })); - Assert.Equal(TaskStatus.RanToCompletion, task.Status); - await task; - - Assert.Equal("Modified value", myValueProperty.GetValue(component)); - } - - [Fact] - public async Task SupportsTwoWayBindingForTextareas() - { - // Arrange/Act - var component = CompileToComponent(@" -@using Microsoft.AspNetCore.Components.Web -<textarea @bind=""MyValue"" ></textarea> -@code { - public string MyValue { get; set; } = ""Initial value""; -}"); - var myValueProperty = component.GetType().GetProperty("MyValue"); - - var renderer = new TestRenderer(); - - // Assert - EventCallback setter = default; - var frames = GetRenderTree(renderer, component); - Assert.Collection(frames, - frame => AssertFrame.Element(frame, "textarea", 3, 0), - frame => AssertFrame.Attribute(frame, "value", "Initial value", 1), - frame => - { - AssertFrame.Attribute(frame, "onchange", 2); - setter = Assert.IsType<EventCallback>(frame.AttributeValue); - }); - - // Trigger the change event to show it updates the property - // - // This should always complete synchronously. - var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = "Modified value", })); - Assert.Equal(TaskStatus.RanToCompletion, task.Status); - await task; - - Assert.Equal("Modified value", myValueProperty.GetValue(component)); - } - - [Fact] - public async Task SupportsTwoWayBindingForDateValues() - { - // Arrange/Act - var component = CompileToComponent(@" -@using Microsoft.AspNetCore.Components.Web -<input @bind=""MyDate"" /> -@code { - public DateTime MyDate { get; set; } = new DateTime(2018, 3, 4, 1, 2, 3); -}"); - var myDateProperty = component.GetType().GetProperty("MyDate"); - - var renderer = new TestRenderer(); - - // Assert - EventCallback setter = default; - var frames = GetRenderTree(renderer, component); - Assert.Collection(frames, - frame => AssertFrame.Element(frame, "input", 3, 0), - frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 3, 4, 1, 2, 3).ToString(), 1), - frame => - { - AssertFrame.Attribute(frame, "onchange", 2); - setter = Assert.IsType<EventCallback>(frame.AttributeValue); - }); - - // Trigger the change event to show it updates the property - // Trigger the change event to show it updates the property - // - // This should always complete synchronously. - var newDateValue = new DateTime(2018, 3, 5, 4, 5, 6); - var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = newDateValue.ToString(), })); - Assert.Equal(TaskStatus.RanToCompletion, task.Status); - await task; - - Assert.Equal(newDateValue, myDateProperty.GetValue(component)); - } - - [Fact] - public async Task SupportsTwoWayBindingForDateValuesWithFormatString() - { - // Arrange/Act - var testDateFormat = "ddd yyyy-MM-dd"; - var component = CompileToComponent($@" -@using Microsoft.AspNetCore.Components.Web -<input @bind=""@MyDate"" @bind:format=""{testDateFormat}"" /> -@code {{ - public DateTime MyDate {{ get; set; }} = new DateTime(2018, 3, 4); -}}"); - var myDateProperty = component.GetType().GetProperty("MyDate"); - - var renderer = new TestRenderer(); - - // Assert - EventCallback setter = default; - var frames = GetRenderTree(renderer, component); - Assert.Collection(frames, - frame => AssertFrame.Element(frame, "input", 3, 0), - frame => AssertFrame.Attribute(frame, "value", new DateTime(2018, 3, 4).ToString(testDateFormat), 1), - frame => - { - AssertFrame.Attribute(frame, "onchange", 2); - setter = Assert.IsType<EventCallback>(frame.AttributeValue); - }); - - // Trigger the change event to show it updates the property - // - // This should always complete synchronously. - var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = new DateTime(2018, 3, 5).ToString(testDateFormat), })); - Assert.Equal(TaskStatus.RanToCompletion, task.Status); - await task; - - Assert.Equal(new DateTime(2018, 3, 5), myDateProperty.GetValue(component)); - } - - [Fact] // In this case, onclick is just a normal HTML attribute - public void SupportsEventHandlerWithString() - { - // Arrange - var component = CompileToComponent(@" -<button onclick=""function(){console.log('hello');};"" />"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection(frames, - frame => AssertFrame.Markup(frame, "<button onclick=\"function(){console.log('hello');};\"></button>", 0)); - } - - [Fact] - public void SupportsEventHandlerWithLambda() - { - // Arrange - var component = CompileToComponent(@" -@using Microsoft.AspNetCore.Components.Web -<button @onclick=""x => Clicked = true"" /> -@code { - public bool Clicked { get; set; } -}"); - - var clicked = component.GetType().GetProperty("Clicked"); - - var renderer = new TestRenderer(); - - // Act - var frames = GetRenderTree(renderer, component); - - // Assert - Assert.Collection(frames, - frame => AssertFrame.Element(frame, "button", 2, 0), - frame => - { - AssertFrame.Attribute(frame, "onclick", 1); - - var func = Assert.IsType<Action<MouseEventArgs>>(frame.AttributeValue); - Assert.False((bool)clicked.GetValue(component)); - - func(new MouseEventArgs()); - Assert.True((bool)clicked.GetValue(component)); - }); - } - - [Fact] - public void SupportsEventHandlerWithMethodGroup() - { - // Arrange - var component = CompileToComponent(@" -@using Microsoft.AspNetCore.Components.Web -<button @onclick=""OnClick"" /> -@code { - public void OnClick(MouseEventArgs e) { Clicked = true; } - public bool Clicked { get; set; } -}"); - - var clicked = component.GetType().GetProperty("Clicked"); - - var renderer = new TestRenderer(); - - // Act - var frames = GetRenderTree(renderer, component); - - // Assert - Action<MouseEventArgs> func = default; // Since this is a method group, we don't need to create an EventCallback - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "button", 2, 0), - frame => - { - AssertFrame.Attribute(frame, "onclick", 1); - - func = Assert.IsType<Action<MouseEventArgs>>(frame.AttributeValue); - Assert.False((bool)clicked.GetValue(component)); - - - }); - - func.Invoke(new MouseEventArgs()); - Assert.True((bool)clicked.GetValue(component)); - } - - [Fact] - public async Task SupportsTwoWayBindingForBoolValues() - { - // Arrange/Act - var component = CompileToComponent(@" -@using Microsoft.AspNetCore.Components.Web -<input @bind=""MyValue"" /> -@code { - public bool MyValue { get; set; } = true; -}"); - var myValueProperty = component.GetType().GetProperty("MyValue"); - - var renderer = new TestRenderer(); - - // Assert - EventCallback setter = default; - var frames = GetRenderTree(renderer, component); - Assert.Collection(frames, - frame => AssertFrame.Element(frame, "input", 3, 0), - frame => AssertFrame.Attribute(frame, "value", true, 1), - frame => - { - AssertFrame.Attribute(frame, "onchange", 2); - setter = Assert.IsType<EventCallback>(frame.AttributeValue); - }); - - // Trigger the change event to show it updates the property - // - // This should always complete synchronously. - var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs() { Value = false, })); - Assert.Equal(TaskStatus.RanToCompletion, task.Status); - await task; - - Assert.False((bool)myValueProperty.GetValue(component)); - } - - [Fact] - public async Task SupportsTwoWayBindingForEnumValues() - { - // Arrange/Act - var myEnumType = FullTypeName<MyEnum>(); - var component = CompileToComponent($@" -@using Microsoft.AspNetCore.Components.Web -<input @bind=""MyValue"" /> -@code {{ - public {myEnumType} MyValue {{ get; set; }} = {myEnumType}.{nameof(MyEnum.FirstValue)}; -}}"); - var myValueProperty = component.GetType().GetProperty("MyValue"); - - var renderer = new TestRenderer(); - - // Assert - EventCallback setter = default; - var frames = GetRenderTree(renderer, component); - Assert.Collection(frames, - frame => AssertFrame.Element(frame, "input", 3, 0), - frame => AssertFrame.Attribute(frame, "value", MyEnum.FirstValue.ToString(), 1), - frame => - { - AssertFrame.Attribute(frame, "onchange", 2); - setter = Assert.IsType<EventCallback>(frame.AttributeValue); - }); - - // Trigger the change event to show it updates the property - // - // This should always complete synchronously. - var task = renderer.Dispatcher.InvokeAsync(() => setter.InvokeAsync(new ChangeEventArgs { Value = MyEnum.SecondValue.ToString(), })); - Assert.Equal(TaskStatus.RanToCompletion, task.Status); - await task; - - Assert.Equal(MyEnum.SecondValue, (MyEnum)myValueProperty.GetValue(component)); - } - - public enum MyEnum { FirstValue, SecondValue } - - [Fact] - public void RazorTemplate_NonGeneric_CanBeUsedFromRazorCode() - { - // Arrange - var component = CompileToComponent(@" -@{ RenderFragment template = @<div>@(""Hello, World!"".ToLower())</div>; } -@for (var i = 0; i < 3; i++) -{ - @template; -} -"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "hello, world!", 1), - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "hello, world!", 1), - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "hello, world!", 1)); - } - - [Fact] - public void RazorTemplate_Generic_CanBeUsedFromRazorCode() - { - // Arrange - var component = CompileToComponent(@" -@{ RenderFragment<string> template = (context) => @<div>@context.ToLower()</div>; } -@for (var i = 0; i < 3; i++) -{ - @template(""Hello, World!""); -} -"); - - // Act - var frames = GetRenderTree(component); - - // Assert - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "hello, world!", 1), - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "hello, world!", 1), - frame => AssertFrame.Element(frame, "div", 2, 0), - frame => AssertFrame.Text(frame, "hello, world!", 1)); - } - - [Fact] - public void RazorTemplate_NonGeneric_CanBeUsedFromMethod() - { - // Arrange - var component = CompileToComponent(@" -@(Repeat(@<div>@(""Hello, World!"".ToLower())</div>, 3)) - -@code { - RenderFragment Repeat(RenderFragment template, int count) - { - return (b) => - { - for (var i = 0; i < count; i++) - { - b.AddContent(i, template); - } - }; - } -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - // - // The sequence numbers start at 1 here because there is an AddContent(0, Repeat(....) call - // that precedes the definition of the lambda. Sequence numbers for the lambda are allocated - // from the same logical sequence as the surrounding code. - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "div", 2, 1), - frame => AssertFrame.Text(frame, "hello, world!", 2), - frame => AssertFrame.Element(frame, "div", 2, 1), - frame => AssertFrame.Text(frame, "hello, world!", 2), - frame => AssertFrame.Element(frame, "div", 2, 1), - frame => AssertFrame.Text(frame, "hello, world!", 2)); - } - - [Fact] - public void RazorTemplate_Generic_CanBeUsedFromMethod() - { - // Arrange - var component = CompileToComponent(@" -@(Repeat((context) => @<div>@context.ToLower()</div>, ""Hello, World!"", 3)) - -@code { - RenderFragment Repeat<T>(RenderFragment<T> template, T value, int count) - { - return (b) => - { - for (var i = 0; i < count; i++) - { - b.AddContent(i, template, value); - } - }; - } -}"); - - // Act - var frames = GetRenderTree(component); - - // Assert - // - // The sequence numbers start at 1 here because there is an AddContent(0, Repeat(....) call - // that precedes the definition of the lambda. Sequence numbers for the lambda are allocated - // from the same logical sequence as the surrounding code. - Assert.Collection( - frames, - frame => AssertFrame.Element(frame, "div", 2, 1), - frame => AssertFrame.Text(frame, "hello, world!", 2), - frame => AssertFrame.Element(frame, "div", 2, 1), - frame => AssertFrame.Text(frame, "hello, world!", 2), - frame => AssertFrame.Element(frame, "div", 2, 1), - frame => AssertFrame.Text(frame, "hello, world!", 2)); - } - } -} diff --git a/src/Components/Blazor/Build/testassets/standalone/standalone.csproj b/src/Components/Blazor/Build/testassets/standalone/standalone.csproj deleted file mode 100644 index 1b13eb3d5357e932e50dad908dbf1d64820537d1..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Build/testassets/standalone/standalone.csproj +++ /dev/null @@ -1,20 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk.Web"> - <Import Project="$(ReferenceBlazorBuildFromSourceProps)" /> - - <PropertyGroup> - <TargetFramework>netstandard2.1</TargetFramework> - <RazorLangVersion>3.0</RazorLangVersion> - </PropertyGroup> - - <!-- Test Placeholder --> - - <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.Components" Version="3.0.0" /> - <PackageReference Include="Microsoft.AspNetCore.Blazor.Mono" Version="$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" /> - </ItemGroup> - - <ItemGroup> - <ProjectReference Include="..\razorclasslibrary\RazorClassLibrary.csproj" /> - </ItemGroup> - -</Project> diff --git a/src/Components/Blazor/Directory.Build.props b/src/Components/Blazor/Directory.Build.props deleted file mode 100644 index a90d83b4cc93c561c2f38cf0965aad6ae5590e0f..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Directory.Build.props +++ /dev/null @@ -1,9 +0,0 @@ -<Project> - <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" /> - - <PropertyGroup> - <!-- Override prerelease label and use preview 4, even in the final build --> - <PreReleaseVersionLabel>$(BlazorClientPreReleaseVersionLabel)</PreReleaseVersionLabel> - <DotNetFinalVersionKind></DotNetFinalVersionKind> - </PropertyGroup> -</Project> diff --git a/src/Components/Blazor/Directory.Build.targets b/src/Components/Blazor/Directory.Build.targets deleted file mode 100644 index e1a17eb9ca9cb3552c0c2f61dab5c5de1efa12be..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Directory.Build.targets +++ /dev/null @@ -1,8 +0,0 @@ -<Project> - <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" /> - - <PropertyGroup> - <ComponentsPackageVersion>$(PackageVersion)</ComponentsPackageVersion> - </PropertyGroup> - -</Project> diff --git a/src/Components/Blazor/Http/src/HttpClientJsonExtensions.cs b/src/Components/Blazor/Http/src/HttpClientJsonExtensions.cs deleted file mode 100644 index db691257aa1037e1bb4f62cf552881e97d4ca191..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Http/src/HttpClientJsonExtensions.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Net.Http; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Components -{ - /// <summary> - /// Extension methods for working with JSON APIs. - /// </summary> - public static class HttpClientJsonExtensions - { - /// <summary> - /// Sends a GET request to the specified URI, and parses the JSON response body - /// to create an object of the generic type. - /// </summary> - /// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam> - /// <param name="httpClient">The <see cref="HttpClient"/>.</param> - /// <param name="requestUri">The URI that the request will be sent to.</param> - /// <returns>The response parsed as an object of the generic type.</returns> - public static async Task<T> GetJsonAsync<T>(this HttpClient httpClient, string requestUri) - { - var stringContent = await httpClient.GetStringAsync(requestUri); - return JsonSerializer.Deserialize<T>(stringContent, JsonSerializerOptionsProvider.Options); - } - - /// <summary> - /// Sends a POST request to the specified URI, including the specified <paramref name="content"/> - /// in JSON-encoded format, and parses the JSON response body to create an object of the generic type. - /// </summary> - /// <param name="httpClient">The <see cref="HttpClient"/>.</param> - /// <param name="requestUri">The URI that the request will be sent to.</param> - /// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param> - /// <returns>The response parsed as an object of the generic type.</returns> - public static Task PostJsonAsync(this HttpClient httpClient, string requestUri, object content) - => httpClient.SendJsonAsync(HttpMethod.Post, requestUri, content); - - /// <summary> - /// Sends a POST request to the specified URI, including the specified <paramref name="content"/> - /// in JSON-encoded format, and parses the JSON response body to create an object of the generic type. - /// </summary> - /// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam> - /// <param name="httpClient">The <see cref="HttpClient"/>.</param> - /// <param name="requestUri">The URI that the request will be sent to.</param> - /// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param> - /// <returns>The response parsed as an object of the generic type.</returns> - public static Task<T> PostJsonAsync<T>(this HttpClient httpClient, string requestUri, object content) - => httpClient.SendJsonAsync<T>(HttpMethod.Post, requestUri, content); - - /// <summary> - /// Sends a PUT request to the specified URI, including the specified <paramref name="content"/> - /// in JSON-encoded format. - /// </summary> - /// <param name="httpClient">The <see cref="HttpClient"/>.</param> - /// <param name="requestUri">The URI that the request will be sent to.</param> - /// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param> - public static Task PutJsonAsync(this HttpClient httpClient, string requestUri, object content) - => httpClient.SendJsonAsync(HttpMethod.Put, requestUri, content); - - /// <summary> - /// Sends a PUT request to the specified URI, including the specified <paramref name="content"/> - /// in JSON-encoded format, and parses the JSON response body to create an object of the generic type. - /// </summary> - /// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam> - /// <param name="httpClient">The <see cref="HttpClient"/>.</param> - /// <param name="requestUri">The URI that the request will be sent to.</param> - /// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param> - /// <returns>The response parsed as an object of the generic type.</returns> - public static Task<T> PutJsonAsync<T>(this HttpClient httpClient, string requestUri, object content) - => httpClient.SendJsonAsync<T>(HttpMethod.Put, requestUri, content); - - /// <summary> - /// Sends an HTTP request to the specified URI, including the specified <paramref name="content"/> - /// in JSON-encoded format. - /// </summary> - /// <param name="httpClient">The <see cref="HttpClient"/>.</param> - /// <param name="method">The HTTP method.</param> - /// <param name="requestUri">The URI that the request will be sent to.</param> - /// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param> - public static Task SendJsonAsync(this HttpClient httpClient, HttpMethod method, string requestUri, object content) - => httpClient.SendJsonAsync<IgnoreResponse>(method, requestUri, content); - - /// <summary> - /// Sends an HTTP request to the specified URI, including the specified <paramref name="content"/> - /// in JSON-encoded format, and parses the JSON response body to create an object of the generic type. - /// </summary> - /// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam> - /// <param name="httpClient">The <see cref="HttpClient"/>.</param> - /// <param name="method">The HTTP method.</param> - /// <param name="requestUri">The URI that the request will be sent to.</param> - /// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param> - /// <returns>The response parsed as an object of the generic type.</returns> - public static async Task<T> SendJsonAsync<T>(this HttpClient httpClient, HttpMethod method, string requestUri, object content) - { - var requestJson = JsonSerializer.Serialize(content, JsonSerializerOptionsProvider.Options); - var response = await httpClient.SendAsync(new HttpRequestMessage(method, requestUri) - { - Content = new StringContent(requestJson, Encoding.UTF8, "application/json") - }); - - // Make sure the call was successful before we - // attempt to process the response content - response.EnsureSuccessStatusCode(); - - if (typeof(T) == typeof(IgnoreResponse)) - { - return default; - } - else - { - var stringContent = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize<T>(stringContent, JsonSerializerOptionsProvider.Options); - } - } - - class IgnoreResponse { } - } -} diff --git a/src/Components/Blazor/Http/src/Microsoft.AspNetCore.Blazor.HttpClient.csproj b/src/Components/Blazor/Http/src/Microsoft.AspNetCore.Blazor.HttpClient.csproj deleted file mode 100644 index 9d6deb6173bbac14197de55e5a47be527ef2fd0f..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Http/src/Microsoft.AspNetCore.Blazor.HttpClient.csproj +++ /dev/null @@ -1,18 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>netstandard2.0</TargetFramework> - <Description>Provides experimental support for using System.Text.Json with HttpClient. Intended for use with Blazor running under WebAssembly.</Description> - <IsShippingPackage>false</IsShippingPackage> - <HasReferenceAssembly>false</HasReferenceAssembly> - </PropertyGroup> - - <ItemGroup> - <Compile Include="..\..\..\Shared\src\JsonSerializerOptionsProvider.cs" /> - </ItemGroup> - - <ItemGroup> - <Reference Include="System.Text.Json" /> - </ItemGroup> - -</Project> diff --git a/src/Components/Blazor/Http/test/HttpClientJsonExtensionsTest.cs b/src/Components/Blazor/Http/test/HttpClientJsonExtensionsTest.cs deleted file mode 100644 index f8d31fd0738d92b5809117d2809be22b9799da7b..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Http/test/HttpClientJsonExtensionsTest.cs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Net; -using System.Net.Http; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace Microsoft.AspNetCore.Components.Test -{ - public class HttpClientJsonExtensionsTest - { - private readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - }; - - const string TestUri = "http://example.com/some/uri"; - - [Fact] - public async Task GetJson_Success() - { - // Arrange - var httpClient = new HttpClient(new TestHttpMessageHandler(req => - { - Assert.Equal(TestUri, req.RequestUri.AbsoluteUri); - return Task.FromResult(CreateJsonResponse(HttpStatusCode.OK, new Person - { - Name = "Abc", - Age = 123 - })); - })); - - // Act - var result = await httpClient.GetJsonAsync<Person>(TestUri); - - // Assert - Assert.Equal("Abc", result.Name); - Assert.Equal(123, result.Age); - } - - [Fact] - public async Task GetJson_Failure() - { - // Arrange - var httpClient = new HttpClient(new TestHttpMessageHandler(req => - { - Assert.Equal(TestUri, req.RequestUri.AbsoluteUri); - return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound)); - })); - - // Act/Assert - var ex = await Assert.ThrowsAsync<HttpRequestException>( - () => httpClient.GetJsonAsync<Person>(TestUri)); - Assert.Contains("404 (Not Found)", ex.Message); - } - - [Theory] - [InlineData("Put")] - [InlineData("Post")] - [InlineData("Patch")] - [InlineData("Delete")] - [InlineData("MyArtificialMethod")] - public async Task SendJson_Success(string httpMethodString) - { - var httpMethod = new HttpMethod(httpMethodString); - var requestContent = new { MyProp = true, OtherProp = "Hello" }; - - // Arrange - var httpClient = new HttpClient(new TestHttpMessageHandler(async req => - { - Assert.Equal(httpMethod, req.Method); - Assert.Equal(TestUri, req.RequestUri.AbsoluteUri); - Assert.Equal(JsonSerializer.Serialize(requestContent, _jsonSerializerOptions), await ((StringContent)req.Content).ReadAsStringAsync()); - return CreateJsonResponse(HttpStatusCode.OK, new Person - { - Name = "Abc", - Age = 123 - }); - })); - - // Act - var result = await Send(httpClient, httpMethodString, requestContent); - - // Assert - Assert.Equal("Abc", result.Name); - Assert.Equal(123, result.Age); - } - - [Fact] - public async Task ReadAsJsonAsync_ReadsCamelCasedJson() - { - var input = "{\"name\": \"TestPerson\", \"age\": 23 }"; - - // Arrange - var httpClient = new HttpClient(new TestHttpMessageHandler(req => - { - return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(input) - }); - })); - - // Act - var result = await httpClient.GetJsonAsync<Person>(TestUri); - - // Assert - Assert.Equal("TestPerson", result.Name); - Assert.Equal(23, result.Age); - } - - [Fact] - public async Task ReadAsJsonAsync_ReadsPascalCasedJson() - { - var input = "{\"Name\": \"TestPerson\", \"Age\": 23 }"; - - // Arrange - var httpClient = new HttpClient(new TestHttpMessageHandler(req => - { - return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(input) - }); - })); - - // Act - var result = await httpClient.GetJsonAsync<Person>(TestUri); - - // Assert - Assert.Equal("TestPerson", result.Name); - Assert.Equal(23, result.Age); - } - - [Theory] - [InlineData("Put")] - [InlineData("Post")] - [InlineData("Patch")] - [InlineData("Delete")] - [InlineData("MyArtificialMethod")] - public async Task SendJson_Failure(string httpMethodString) - { - var httpMethod = new HttpMethod(httpMethodString); - var requestContent = new { MyProp = true, OtherProp = "Hello" }; - - // Arrange - var httpClient = new HttpClient(new TestHttpMessageHandler(async req => - { - Assert.Equal(httpMethod, req.Method); - Assert.Equal(TestUri, req.RequestUri.AbsoluteUri); - Assert.Equal(JsonSerializer.Serialize(requestContent, _jsonSerializerOptions), await ((StringContent)req.Content).ReadAsStringAsync()); - return new HttpResponseMessage(HttpStatusCode.BadGateway); - })); - - // Act/Assert - var ex = await Assert.ThrowsAsync<HttpRequestException>( - () => Send(httpClient, httpMethodString, requestContent)); - Assert.Contains("502 (Bad Gateway)", ex.Message); - } - - HttpResponseMessage CreateJsonResponse(HttpStatusCode statusCode, object content) - { - return new HttpResponseMessage(statusCode) - { - Content = new StringContent(JsonSerializer.Serialize(content, _jsonSerializerOptions)) - }; - } - - Task<Person> Send(HttpClient httpClient, string httpMethodString, object requestContent) - { - // For methods with convenience overloads, show those overloads work - switch (httpMethodString) - { - case "post": - return httpClient.PostJsonAsync<Person>(TestUri, requestContent); - case "put": - return httpClient.PutJsonAsync<Person>(TestUri, requestContent); - default: - return httpClient.SendJsonAsync<Person>(new HttpMethod(httpMethodString), TestUri, requestContent); - } - } - - class Person - { - public string Name { get; set; } - public int Age { get; set; } - } - - class TestHttpMessageHandler : HttpMessageHandler - { - private readonly Func<HttpRequestMessage, Task<HttpResponseMessage>> _sendDelegate; - - public TestHttpMessageHandler(Func<HttpRequestMessage, Task<HttpResponseMessage>> sendDelegate) - { - _sendDelegate = sendDelegate; - } - - protected override void Dispose(bool disposing) - => base.Dispose(disposing); - - protected override Task<HttpResponseMessage> SendAsync( - HttpRequestMessage request, CancellationToken cancellationToken) - => _sendDelegate(request); - } - } -} diff --git a/src/Components/Blazor/Http/test/Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj b/src/Components/Blazor/Http/test/Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj deleted file mode 100644 index e7a9870ecde1f84ed5c7b526f11e9354cca2d1d1..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Http/test/Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj +++ /dev/null @@ -1,11 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> - </PropertyGroup> - - <ItemGroup> - <Reference Include="Microsoft.AspNetCore.Blazor.HttpClient" /> - </ItemGroup> - -</Project> diff --git a/src/Components/Blazor/Server/src/AutoRebuild/AutoRebuildExtensions.cs b/src/Components/Blazor/Server/src/AutoRebuild/AutoRebuildExtensions.cs deleted file mode 100644 index 6a43600bb91d6a27d6d2ae0b329688e7469f5ed5..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Server/src/AutoRebuild/AutoRebuildExtensions.cs +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Blazor.Server; -using Microsoft.AspNetCore.Blazor.Server.AutoRebuild; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Builder -{ - internal static class AutoRebuildExtensions - { - // Note that we don't need to watch typical static-file extensions (.css, .js, etc.) - // because anything in wwwroot is just served directly from disk on each reload. - // TODO: Make the set of extensions and exclusions configurable in csproj - private static string[] _includedSuffixes = new[] { ".cs", ".cshtml" }; - private static string[] _excludedDirectories = new[] { "obj", "bin" }; - - // To ensure the FileSystemWatchers aren't collected, reference them - // in this static list. They never need to be removed because there's no - // way to remove middleware once it's registered. - private static List<object> _uncollectableWatchers = new List<object>(); - - public static void UseHostedAutoRebuild(this IApplicationBuilder app, BlazorConfig config, string hostAppContentRootPath) - { - var isFirstFileWrite = true; - WatchFileSystem(config, () => - { - if (isFirstFileWrite) - { - try - { - // Touch any .cs file to force the host project to rebuild - // (which in turn rebuilds the client, since it's referenced) - var fileToTouch = Directory.EnumerateFiles( - hostAppContentRootPath, - "*.cs", - SearchOption.AllDirectories).FirstOrDefault(); - - if (!string.IsNullOrEmpty(fileToTouch)) - { - File.SetLastWriteTime(fileToTouch, DateTime.Now); - } - } - catch (Exception ex) - { - // If we don't have permission to write these files, autorebuild will not be enabled - var loggerFactory = app.ApplicationServices.GetRequiredService<ILoggerFactory>(); - var logger = loggerFactory.CreateLogger(typeof (AutoRebuildExtensions)); - logger?.LogWarning(ex, - "Cannot autorebuild because there was an error when writing to a file in '{0}'.", - hostAppContentRootPath); - } - - isFirstFileWrite = false; - } - }); - } - - public static void UseDevServerAutoRebuild(this IApplicationBuilder app, BlazorConfig config) - { - // Currently this only supports VS for Windows. Later on we can add - // an IRebuildService implementation for VS for Mac, etc. - if (!VSForWindowsRebuildService.TryCreate(out var rebuildService)) - { - return; // You're not on Windows, or you didn't launch this process from VS - } - - // Assume we're up to date when the app starts. - var buildToken = new RebuildToken(new DateTime(1970, 1, 1)) { BuildTask = Task.CompletedTask, }; - - WatchFileSystem(config, () => - { - // Don't start the recompilation immediately. We only start it when the next - // HTTP request arrives, because it's annoying if the IDE is constantly rebuilding - // when you're making changes to multiple files and aren't ready to reload - // in the browser yet. - // - // Replacing the token means that new requests that come in will trigger a rebuild, - // and will all 'join' that build until a new file change occurs. - buildToken = new RebuildToken(DateTime.Now); - }); - - app.Use(async (context, next) => - { - try - { - var token = buildToken; - if (token.BuildTask == null) - { - // The build is out of date, but a new build is not yet started. - // - // We can count on VS to only allow one build at a time, this is a safe race - // because if we request a second concurrent build, it will 'join' the current one. - var task = rebuildService.PerformRebuildAsync( - config.SourceMSBuildPath, - token.LastChange); - token.BuildTask = task; - } - - // In the general case it's safe to await this task, it will be a completed task - // if everything is up to date. - await token.BuildTask; - } - catch (Exception) - { - // If there's no listener on the other end of the pipe, or if anything - // else goes wrong, we just let the incoming request continue. - // There's nowhere useful to log this information so if people report - // problems we'll just have to get a repro and debug it. - // If it was an error on the VS side, it logs to the output window. - } - - await next(); - }); - } - - private static void WatchFileSystem(BlazorConfig config, Action onWrite) - { - var clientAppRootDir = Path.GetDirectoryName(config.SourceMSBuildPath); - var excludePathPrefixes = _excludedDirectories.Select(subdir - => Path.Combine(clientAppRootDir, subdir) + Path.DirectorySeparatorChar); - - var fsw = new FileSystemWatcher(clientAppRootDir); - fsw.Created += OnEvent; - fsw.Changed += OnEvent; - fsw.Deleted += OnEvent; - fsw.Renamed += OnEvent; - fsw.IncludeSubdirectories = true; - fsw.EnableRaisingEvents = true; - - // Ensure the watcher is not GCed for as long as the app lives - lock (_uncollectableWatchers) - { - _uncollectableWatchers.Add(fsw); - } - - void OnEvent(object sender, FileSystemEventArgs eventArgs) - { - if (!File.Exists(eventArgs.FullPath)) - { - // It's probably a directory rather than a file - return; - } - - if (!_includedSuffixes.Any(ext => eventArgs.Name.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) - { - // Not a candidate file type - return; - } - - if (excludePathPrefixes.Any(prefix => eventArgs.FullPath.StartsWith(prefix, StringComparison.Ordinal))) - { - // In an excluded subdirectory - return; - } - - onWrite(); - } - } - - // Represents a three-state value for the state of the build - // - // BuildTask == null means the build is out of date, but no build has started - // BuildTask.IsCompleted == false means the build has been started, but has not completed - // BuildTask.IsCompleted == true means the build has completed - private class RebuildToken - { - public RebuildToken(DateTime lastChange) - { - LastChange = lastChange; - } - - public DateTime LastChange { get; } - - public Task BuildTask; - } - } -} diff --git a/src/Components/Blazor/Server/src/AutoRebuild/IRebuildService.cs b/src/Components/Blazor/Server/src/AutoRebuild/IRebuildService.cs deleted file mode 100644 index 6e9616e4bf8b79ad2ff79d27d4282d3819c6bc68..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Server/src/AutoRebuild/IRebuildService.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild -{ - /// <summary> - /// Represents a mechanism for rebuilding a .NET project. For example, it - /// could be a way of signalling to a VS process to perform a build. - /// </summary> - internal interface IRebuildService - { - Task<bool> PerformRebuildAsync(string projectFullPath, DateTime ifNotBuiltSince); - } -} diff --git a/src/Components/Blazor/Server/src/AutoRebuild/ProcessUtils.cs b/src/Components/Blazor/Server/src/AutoRebuild/ProcessUtils.cs deleted file mode 100644 index af63853ed9aaf4a60f0737c95e1e588823a29b5a..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Server/src/AutoRebuild/ProcessUtils.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild -{ - internal static class ProcessUtils - { - // Based on https://stackoverflow.com/a/3346055 - - public static Process GetParent(Process process) - { - var result = new ProcessBasicInformation(); - var handle = process.Handle; - var status = NtQueryInformationProcess(handle, 0, ref result, Marshal.SizeOf(result), out var returnLength); - if (status != 0) - { - throw new Win32Exception(status); - } - - try - { - var parentProcessId = result.InheritedFromUniqueProcessId.ToInt32(); - return parentProcessId > 0 ? Process.GetProcessById(parentProcessId) : null; - } - catch (ArgumentException) - { - return null; // Process not found - } - } - - [DllImport("ntdll.dll")] - private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ProcessBasicInformation processInformation, int processInformationLength, out int returnLength); - - [StructLayout(LayoutKind.Sequential)] - struct ProcessBasicInformation - { - // These members must match PROCESS_BASIC_INFORMATION - public IntPtr Reserved1; - public IntPtr PebBaseAddress; - public IntPtr Reserved2_0; - public IntPtr Reserved2_1; - public IntPtr UniqueProcessId; - public IntPtr InheritedFromUniqueProcessId; - } - } -} diff --git a/src/Components/Blazor/Server/src/AutoRebuild/StreamProtocolExtensions.cs b/src/Components/Blazor/Server/src/AutoRebuild/StreamProtocolExtensions.cs deleted file mode 100644 index 394b26073d48214bd782790aaafaa5dbc65c86ca..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Server/src/AutoRebuild/StreamProtocolExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild -{ - internal static class StreamProtocolExtensions - { - public static async Task WriteStringAsync(this Stream stream, string str) - { - var utf8Bytes = Encoding.UTF8.GetBytes(str); - await stream.WriteAsync(BitConverter.GetBytes(utf8Bytes.Length), 0, 4); - await stream.WriteAsync(utf8Bytes, 0, utf8Bytes.Length); - } - - public static async Task WriteDateTimeAsync(this Stream stream, DateTime value) - { - var ticksBytes = BitConverter.GetBytes(value.Ticks); - await stream.WriteAsync(ticksBytes, 0, 8); - } - - public static async Task<bool> ReadBoolAsync(this Stream stream) - { - var responseBuf = new byte[1]; - await stream.ReadAsync(responseBuf, 0, 1); - return responseBuf[0] == 1; - } - - public static async Task<int> ReadIntAsync(this Stream stream) - { - var responseBuf = new byte[4]; - await stream.ReadAsync(responseBuf, 0, 4); - return BitConverter.ToInt32(responseBuf, 0); - } - } -} diff --git a/src/Components/Blazor/Server/src/AutoRebuild/VSForWindowsRebuildService.cs b/src/Components/Blazor/Server/src/AutoRebuild/VSForWindowsRebuildService.cs deleted file mode 100644 index 92d99d57dd182ea70c18448016d381a6fb3186b3..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Server/src/AutoRebuild/VSForWindowsRebuildService.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.IO.Pipes; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild -{ - /// <summary> - /// Finds the VS process that launched this app process (if any), and uses - /// named pipes to communicate with its AutoRebuild listener (if any). - /// </summary> - internal class VSForWindowsRebuildService : IRebuildService - { - private const int _connectionTimeoutMilliseconds = 3000; - private readonly Process _vsProcess; - - public static bool TryCreate(out VSForWindowsRebuildService result) - { - var vsProcess = FindAncestorVSProcess(); - if (vsProcess != null) - { - result = new VSForWindowsRebuildService(vsProcess); - return true; - } - else - { - result = null; - return false; - } - } - - public async Task<bool> PerformRebuildAsync(string projectFullPath, DateTime ifNotBuiltSince) - { - var pipeName = $"BlazorAutoRebuild\\{_vsProcess.Id}"; - using (var pipeClient = new NamedPipeClientStream(pipeName)) - { - await pipeClient.ConnectAsync(_connectionTimeoutMilliseconds); - - // Protocol: - // 1. Receive protocol version number from the VS listener - // If we're incompatible with it, send back special string "abort" and end - // 2. Send the project path to the VS listener - // 3. Send the 'if not rebuilt since' timestamp to the VS listener - // 4. Wait for it to send back a bool representing the result - // Keep in sync with AutoRebuildService.cs in the BlazorExtension project - // In the future we may extend this to getting back build error details - var remoteProtocolVersion = await pipeClient.ReadIntAsync(); - if (remoteProtocolVersion == 1) - { - await pipeClient.WriteStringAsync(projectFullPath); - await pipeClient.WriteDateTimeAsync(ifNotBuiltSince); - return await pipeClient.ReadBoolAsync(); - } - else - { - await pipeClient.WriteStringAsync("abort"); - return false; - } - } - } - - private VSForWindowsRebuildService(Process vsProcess) - { - _vsProcess = vsProcess ?? throw new ArgumentNullException(nameof(vsProcess)); - } - - private static Process FindAncestorVSProcess() - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return null; - } - - var candidateProcess = Process.GetCurrentProcess(); - try - { - while (candidateProcess != null && !candidateProcess.HasExited) - { - // It's unlikely that anyone's going to have a non-VS process in the process - // hierarchy called 'devenv', but if that turns out to be a scenario, we could - // (for example) write the VS PID to the obj directory during build, and then - // only consider processes with that ID. We still want to be sure there really - // is such a process in our ancestor chain, otherwise if you did "dotnet run" - // in a command prompt, we'd be confused and think it was launched from VS. - if (candidateProcess.ProcessName.Equals("devenv", StringComparison.OrdinalIgnoreCase)) - { - return candidateProcess; - } - - candidateProcess = ProcessUtils.GetParent(candidateProcess); - } - } - catch (Exception) - { - // There's probably some permissions issue that prevents us from seeing - // further up the ancestor list, so we have to stop looking here. - } - - return null; - } - } -} diff --git a/src/Components/Blazor/Server/src/BlazorConfig.cs b/src/Components/Blazor/Server/src/BlazorConfig.cs deleted file mode 100644 index 5438a7e36a531d417b72a785ca5a4d043d815c5f..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Server/src/BlazorConfig.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Linq; - -namespace Microsoft.AspNetCore.Blazor.Server -{ - internal class BlazorConfig - { - public string SourceMSBuildPath { get; } - public string SourceOutputAssemblyPath { get; } - public string WebRootPath { get; } - public string DistPath - => Path.Combine(Path.GetDirectoryName(SourceOutputAssemblyPath), "dist"); - public bool EnableAutoRebuilding { get; } - public bool EnableDebugging { get; } - - public static BlazorConfig Read(string assemblyPath) - => new BlazorConfig(assemblyPath); - - private BlazorConfig(string assemblyPath) - { - // TODO: Instead of assuming the lines are in a specific order, either JSON-encode - // the whole thing, or at least give the lines key prefixes (e.g., "reload:<someuri>") - // so we're not dependent on order and all lines being present. - - var configFilePath = Path.ChangeExtension(assemblyPath, ".blazor.config"); - var configLines = File.ReadLines(configFilePath).ToList(); - SourceMSBuildPath = configLines[0]; - - if (SourceMSBuildPath == ".") - { - SourceMSBuildPath = assemblyPath; - } - - var sourceMsBuildDir = Path.GetDirectoryName(SourceMSBuildPath); - SourceOutputAssemblyPath = Path.Combine(sourceMsBuildDir, configLines[1]); - - var webRootPath = Path.Combine(sourceMsBuildDir, "wwwroot"); - if (Directory.Exists(webRootPath)) - { - WebRootPath = webRootPath; - } - - EnableAutoRebuilding = configLines.Contains("autorebuild:true", StringComparer.Ordinal); - EnableDebugging = configLines.Contains("debug:true", StringComparer.Ordinal); - } - - public string FindIndexHtmlFile() - { - // Before publishing, the client project may have a wwwroot directory. - // If so, and if it contains index.html, use that. - if (!string.IsNullOrEmpty(WebRootPath)) - { - var wwwrootIndexHtmlPath = Path.Combine(WebRootPath, "index.html"); - if (File.Exists(wwwrootIndexHtmlPath)) - { - return wwwrootIndexHtmlPath; - } - } - - // After publishing, the client project won't have a wwwroot directory. - // The contents from that dir will have been copied to "dist" during publish. - // So if "dist/index.html" now exists, use that. - var distIndexHtmlPath = Path.Combine(DistPath, "index.html"); - if (File.Exists(distIndexHtmlPath)) - { - return distIndexHtmlPath; - } - - // Since there's no index.html, we'll use the default DefaultPageStaticFileOptions, - // hence we'll look for index.html in the host server app's wwwroot. - return null; - } - } -} diff --git a/src/Components/Blazor/Server/src/Builder/BlazorHostingApplicationBuilderExtensions.cs b/src/Components/Blazor/Server/src/Builder/BlazorHostingApplicationBuilderExtensions.cs deleted file mode 100644 index 74cb3c7b21debb587bac43b9213254b804c80514..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Server/src/Builder/BlazorHostingApplicationBuilderExtensions.cs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Net.Mime; -using Microsoft.AspNetCore.Blazor.Server; -using Microsoft.AspNetCore.StaticFiles; -using Microsoft.Extensions.FileProviders; - -namespace Microsoft.AspNetCore.Builder -{ - /// <summary> - /// Provides extension methods for hosting client-side Blazor applications in ASP.NET Core. - /// </summary> - public static class BlazorHostingApplicationBuilderExtensions - { - /// <summary> - /// Adds a <see cref="StaticFileMiddleware"/> that will serve static files from the client-side Blazor application - /// specified by <typeparamref name="TClientApp"/>. - /// </summary> - /// <typeparam name="TClientApp">A type in the client-side application.</typeparam> - /// <param name="app">The <see cref="IApplicationBuilder"/>.</param> - /// <returns>The <see cref="IApplicationBuilder"/>.</returns> - public static IApplicationBuilder UseClientSideBlazorFiles<TClientApp>(this IApplicationBuilder app) - { - if (app == null) - { - throw new ArgumentNullException(nameof(app)); - } - - UseClientSideBlazorFiles(app, typeof(TClientApp).Assembly.Location); - return app; - } - - /// <summary> - /// Adds a <see cref="StaticFileMiddleware"/> that will serve static files from the client-side Blazor application - /// specified by <paramref name="clientAssemblyFilePath"/>. - /// </summary> - /// <param name="clientAssemblyFilePath">The file path of the client-side Blazor application assembly.</param> - /// <param name="app">The <see cref="IApplicationBuilder"/>.</param> - /// <returns>The <see cref="IApplicationBuilder"/>.</returns> - public static IApplicationBuilder UseClientSideBlazorFiles(this IApplicationBuilder app, string clientAssemblyFilePath) - { - if (clientAssemblyFilePath == null) - { - throw new ArgumentNullException(nameof(clientAssemblyFilePath)); - } - - var fileProviders = new List<IFileProvider>(); - - // TODO: Make the .blazor.config file contents sane - // Currently the items in it are bizarre and don't relate to their purpose, - // hence all the path manipulation here. We shouldn't be hardcoding 'dist' here either. - var config = BlazorConfig.Read(clientAssemblyFilePath); - - // First, match the request against files in the client app dist directory - fileProviders.Add(new PhysicalFileProvider(config.DistPath)); - - // * Before publishing, we serve the wwwroot files directly from source - // (and don't require them to be copied into dist). - // In this case, WebRootPath will be nonempty if that directory exists. - // * After publishing, the wwwroot files are already copied to 'dist' and - // will be served by the above middleware, so we do nothing here. - // In this case, WebRootPath will be empty (the publish process sets this). - if (!string.IsNullOrEmpty(config.WebRootPath)) - { - fileProviders.Add(new PhysicalFileProvider(config.WebRootPath)); - } - - // We can't modify an IFileContentTypeProvider, so we have to decorate. - var contentTypeProvider = new FileExtensionContentTypeProvider(); - AddMapping(contentTypeProvider, ".dll", MediaTypeNames.Application.Octet); - if (config.EnableDebugging) - { - AddMapping(contentTypeProvider, ".pdb", MediaTypeNames.Application.Octet); - } - - var options = new StaticFileOptions() - { - ContentTypeProvider = contentTypeProvider, - FileProvider = new CompositeFileProvider(fileProviders), - OnPrepareResponse = CacheHeaderSettings.SetCacheHeaders, - }; - - app.UseStaticFiles(options); - return app; - - static void AddMapping(FileExtensionContentTypeProvider provider, string name, string mimeType) - { - if (!provider.Mappings.ContainsKey(name)) - { - provider.Mappings.Add(name, mimeType); - } - } - } - } -} diff --git a/src/Components/Blazor/Server/src/Builder/BlazorHostingEndpointRouteBuilderExtensions.cs b/src/Components/Blazor/Server/src/Builder/BlazorHostingEndpointRouteBuilderExtensions.cs deleted file mode 100644 index f899f2576fdd56e984e59f2c071f796229c9515e..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Server/src/Builder/BlazorHostingEndpointRouteBuilderExtensions.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using Microsoft.AspNetCore.Blazor.Server; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.StaticFiles; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.FileProviders; - -namespace Microsoft.AspNetCore.Builder -{ - /// <summary> - /// Provides extension methods for hosting client-side Blazor applications in ASP.NET Core. - /// </summary> - public static class BlazorHostingEndpointRouteBuilderExtensions - { - /// <summary> - /// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side - /// Blazor application specified by <typeparamref name="TClientApp"/>. - /// </summary> - /// <typeparam name="TClientApp">A type in the client-side application.</typeparam> - /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param> - /// <param name="filePath"> - /// The relative path to the entry point of the client-side application. The path is relative to the - /// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>. - /// </param> - /// <returns>The <see cref="IApplicationBuilder"/>.</returns> - /// <remarks> - /// <para> - /// This method is intended to handle cases where URL path of the request does not contain a filename, and no other - /// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor - /// application, while also allowing requests for non-existent files to result in an HTTP 404. - /// </para> - /// </remarks> - public static IEndpointConventionBuilder MapFallbackToClientSideBlazor<TClientApp>(this IEndpointRouteBuilder endpoints, string filePath) - { - if (endpoints == null) - { - throw new ArgumentNullException(nameof(endpoints)); - } - - if (filePath == null) - { - throw new ArgumentNullException(nameof(filePath)); - } - - return MapFallbackToClientSideBlazor(endpoints, typeof(TClientApp).Assembly.Location, FallbackEndpointRouteBuilderExtensions.DefaultPattern, filePath); - } - - /// <summary> - /// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side - /// Blazor application specified by <paramref name="clientAssemblyFilePath"/>. - /// </summary> - /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param> - /// <param name="clientAssemblyFilePath">The file path of the client-side Blazor application assembly.</param> - /// <param name="filePath"> - /// The relative path to the entry point of the client-side application. The path is relative to the - /// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>. - /// </param> - /// <returns>The <see cref="IApplicationBuilder"/>.</returns> - /// <remarks> - /// <para> - /// This method is intended to handle cases where URL path of the request does not contain a filename, and no other - /// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor - /// application, while also allowing requests for non-existent files to result in an HTTP 404. - /// </para> - /// </remarks> - public static IEndpointConventionBuilder MapFallbackToClientSideBlazor(this IEndpointRouteBuilder endpoints, string clientAssemblyFilePath, string filePath) - { - if (endpoints == null) - { - throw new ArgumentNullException(nameof(endpoints)); - } - - if (clientAssemblyFilePath == null) - { - throw new ArgumentNullException(nameof(clientAssemblyFilePath)); - } - - if (filePath == null) - { - throw new ArgumentNullException(nameof(filePath)); - } - - return MapFallbackToClientSideBlazor(endpoints, clientAssemblyFilePath, FallbackEndpointRouteBuilderExtensions.DefaultPattern, filePath); - } - - /// <summary> - /// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side - /// Blazor application specified by <typeparamref name="TClientApp"/>. - /// </summary> - /// <typeparam name="TClientApp">A type in the client-side application.</typeparam> - /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param> - /// <param name="pattern">The route pattern to match.</param> - /// <param name="filePath"> - /// The relative path to the entry point of the client-side application. The path is relative to the - /// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>. - /// </param> - /// <returns>The <see cref="IApplicationBuilder"/>.</returns> - /// <remarks> - /// <para> - /// This method is intended to handle cases where URL path of the request does not contain a filename, and no other - /// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor - /// application, while also allowing requests for non-existent files to result in an HTTP 404. - /// </para> - /// </remarks> - public static IEndpointConventionBuilder MapFallbackToClientSideBlazor<TClientApp>(this IEndpointRouteBuilder endpoints, string pattern, string filePath) - { - if (endpoints == null) - { - throw new ArgumentNullException(nameof(endpoints)); - } - - if (pattern == null) - { - throw new ArgumentNullException(nameof(pattern)); - } - - if (filePath == null) - { - throw new ArgumentNullException(nameof(filePath)); - } - - return MapFallbackToClientSideBlazor(endpoints, typeof(TClientApp).Assembly.Location, pattern, filePath); - } - - /// <summary> - /// Adds a low-priority endpoint that will serve the the file specified by <paramref name="filePath"/> from the client-side - /// Blazor application specified by <paramref name="clientAssemblyFilePath"/>. - /// </summary> - /// <param name="clientAssemblyFilePath">The file path of the client-side Blazor application assembly.</param> - /// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param> - /// <param name="pattern">The route pattern to match.</param> - /// <param name="filePath"> - /// The relative path to the entry point of the client-side application. The path is relative to the - /// <see cref="IWebHostEnvironment.WebRootPath"/>, commonly <c>wwwroot</c>. - /// </param> - /// <returns>The <see cref="IApplicationBuilder"/>.</returns> - /// <remarks> - /// <para> - /// This method is intended to handle cases where URL path of the request does not contain a filename, and no other - /// endpoint has matched. This is convenient for routing requests for dynamic content to the client-side blazor - /// application, while also allowing requests for non-existent files to result in an HTTP 404. - /// </para> - /// </remarks> - public static IEndpointConventionBuilder MapFallbackToClientSideBlazor(this IEndpointRouteBuilder endpoints, string clientAssemblyFilePath, string pattern, string filePath) - { - if (endpoints == null) - { - throw new ArgumentNullException(nameof(endpoints)); - } - - if (clientAssemblyFilePath == null) - { - throw new ArgumentNullException(nameof(clientAssemblyFilePath)); - } - - if (pattern == null) - { - throw new ArgumentNullException(nameof(pattern)); - } - - if (filePath == null) - { - throw new ArgumentNullException(nameof(filePath)); - } - - var config = BlazorConfig.Read(clientAssemblyFilePath); - - // We want to serve "index.html" from whichever directory contains it in this priority order: - // 1. Client app "dist" directory - // 2. Client app "wwwroot" directory - // 3. Server app "wwwroot" directory - var directory = endpoints.ServiceProvider.GetRequiredService<IWebHostEnvironment>().WebRootPath; - var indexHtml = config.FindIndexHtmlFile(); - if (indexHtml != null) - { - directory = Path.GetDirectoryName(indexHtml); - } - - var options = new StaticFileOptions() - { - FileProvider = new PhysicalFileProvider(directory), - OnPrepareResponse = CacheHeaderSettings.SetCacheHeaders, - }; - - return endpoints.MapFallbackToFile(pattern, filePath, options); - } - } -} diff --git a/src/Components/Blazor/Server/src/Microsoft.AspNetCore.Blazor.Server.csproj b/src/Components/Blazor/Server/src/Microsoft.AspNetCore.Blazor.Server.csproj deleted file mode 100644 index 7596c1a8cb774744a69a6ea7253bf512d9a3db13..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Server/src/Microsoft.AspNetCore.Blazor.Server.csproj +++ /dev/null @@ -1,23 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> - <Description>Runtime server features for ASP.NET Core Blazor applications.</Description> - <IsShippingPackage>false</IsShippingPackage> - <HasReferenceAssembly>false</HasReferenceAssembly> - <!-- This is so that we add the FrameworkReference to Microsoft.AspNetCore.App --> - <UseLatestAspNetCoreReference>true</UseLatestAspNetCoreReference> - </PropertyGroup> - - <ItemGroup> - <Compile Include="$(ComponentsSharedSourceRoot)\src\CacheHeaderSettings.cs" Link="Shared\CacheHeaderSettings.cs" /> - </ItemGroup> - - <ItemGroup> - <Reference Include="Newtonsoft.Json" /> - - <!-- Used by ws-proxy sources only. Remove this once we're able to consume ws-proxy as a NuGet package. --> - <Reference Include="Mono.Cecil" /> - </ItemGroup> - -</Project> diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/BlazorMonoDebugProxyAppBuilderExtensions.cs b/src/Components/Blazor/Server/src/MonoDebugProxy/BlazorMonoDebugProxyAppBuilderExtensions.cs deleted file mode 100644 index cbe0fe363a67ce3870dd42c6ba0fc3fb9968b164..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Server/src/MonoDebugProxy/BlazorMonoDebugProxyAppBuilderExtensions.cs +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Runtime.InteropServices; -using System.Text.Json; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using WsProxy; - -namespace Microsoft.AspNetCore.Builder -{ - /// <summary> - /// Provides infrastructure for debugging Blazor applications. - /// </summary> - public static class BlazorMonoDebugProxyAppBuilderExtensions - { - private static JsonSerializerOptions JsonOptions = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - IgnoreNullValues = true - }; - - private static string DefaultDebuggerHost = "http://localhost:9222"; - - /// <summary> - /// Adds middleware for needed for debugging Blazor applications - /// inside Chromium dev tools. - /// </summary> - public static void UseBlazorDebugging(this IApplicationBuilder app) - { - app.UseWebSockets(); - - app.UseVisualStudioDebuggerConnectionRequestHandlers(); - - app.Use((context, next) => - { - var requestPath = context.Request.Path; - if (!requestPath.StartsWithSegments("/_framework/debug")) - { - return next(); - } - - if (requestPath.Equals("/_framework/debug/ws-proxy", StringComparison.OrdinalIgnoreCase)) - { - return DebugWebSocketProxyRequest(context); - } - - if (requestPath.Equals("/_framework/debug", StringComparison.OrdinalIgnoreCase)) - { - return DebugHome(context); - } - - context.Response.StatusCode = (int)HttpStatusCode.NotFound; - return Task.CompletedTask; - }); - } - - private static string GetDebuggerHost() - { - var envVar = Environment.GetEnvironmentVariable("ASPNETCORE_WEBASSEMBLYDEBUGHOST"); - - if (string.IsNullOrEmpty(envVar)) - { - return DefaultDebuggerHost; - } - else - { - return envVar; - } - } - - private static int GetDebuggerPort() - { - var host = GetDebuggerHost(); - return new Uri(host).Port; - } - - private static void UseVisualStudioDebuggerConnectionRequestHandlers(this IApplicationBuilder app) - { - // Unfortunately VS doesn't send any deliberately distinguishing information so we know it's - // not a regular browser or API client. The closest we can do is look for the *absence* of a - // User-Agent header. In the future, we should try to get VS to send a special header to indicate - // this is a debugger metadata request. - app.Use(async (context, next) => - { - var request = context.Request; - var requestPath = request.Path; - if (requestPath.StartsWithSegments("/json") - && !request.Headers.ContainsKey("User-Agent")) - { - if (requestPath.Equals("/json", StringComparison.OrdinalIgnoreCase) || requestPath.Equals("/json/list", StringComparison.OrdinalIgnoreCase)) - { - var availableTabs = await GetOpenedBrowserTabs(); - - // Filter the list to only include tabs displaying the requested app, - // but only during the "choose application to debug" phase. We can't apply - // the same filter during the "connecting" phase (/json/list), nor do we need to. - if (requestPath.Equals("/json")) - { - availableTabs = availableTabs.Where(tab => tab.Url.StartsWith($"{request.Scheme}://{request.Host}{request.PathBase}/")); - } - - var proxiedTabInfos = availableTabs.Select(tab => - { - var underlyingV8Endpoint = tab.WebSocketDebuggerUrl; - var proxiedV8Endpoint = $"ws://{request.Host}{request.PathBase}/_framework/debug/ws-proxy?browser={WebUtility.UrlEncode(underlyingV8Endpoint)}"; - return new - { - description = "", - devtoolsFrontendUrl = "", - id = tab.Id, - title = tab.Title, - type = tab.Type, - url = tab.Url, - webSocketDebuggerUrl = proxiedV8Endpoint - }; - }); - - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonSerializer.Serialize(proxiedTabInfos)); - } - else if (requestPath.Equals("/json/version", StringComparison.OrdinalIgnoreCase)) - { - var browserVersionJson = await GetBrowserVersionInfoAsync(); - - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(browserVersionJson); - } - } - else - { - await next(); - } - }); - } - - private static async Task DebugWebSocketProxyRequest(HttpContext context) - { - if (!context.WebSockets.IsWebSocketRequest) - { - context.Response.StatusCode = 400; - return; - } - - var browserUri = new Uri(context.Request.Query["browser"]); - var ideSocket = await context.WebSockets.AcceptWebSocketAsync(); - await new MonoProxy().Run(browserUri, ideSocket); - } - - private static async Task DebugHome(HttpContext context) - { - context.Response.ContentType = "text/html"; - - var request = context.Request; - var appRootUrl = $"{request.Scheme}://{request.Host}{request.PathBase}/"; - var targetTabUrl = request.Query["url"]; - if (string.IsNullOrEmpty(targetTabUrl)) - { - context.Response.StatusCode = (int)HttpStatusCode.BadRequest; - await context.Response.WriteAsync("No value specified for 'url'"); - return; - } - - // TODO: Allow overriding port (but not hostname, as we're connecting to the - // local browser, not to the webserver serving the app) - var debuggerHost = GetDebuggerHost(); - var debuggerTabsListUrl = $"{debuggerHost}/json"; - IEnumerable<BrowserTab> availableTabs; - - try - { - availableTabs = await GetOpenedBrowserTabs(); - } - catch (Exception ex) - { - await context.Response.WriteAsync($@" -<h1>Unable to find debuggable browser tab</h1> -<p> - Could not get a list of browser tabs from <code>{debuggerTabsListUrl}</code>. - Ensure your browser is running with debugging enabled. -</p> -<h2>Resolution</h2> -<p> - <h4>If you are using Google Chrome for your development, follow these instructions:</h4> - {GetLaunchChromeInstructions(appRootUrl)} -</p> -<p> - <h4>If you are using Microsoft Edge (Chromium) for your development, follow these instructions:</h4> - {GetLaunchEdgeInstructions(appRootUrl)} -</p> -<strong>This should launch a new browser window with debugging enabled..</p> -<h2>Underlying exception:</h2> -<pre>{ex}</pre> - "); - - return; - } - - var matchingTabs = availableTabs - .Where(t => t.Url.Equals(targetTabUrl, StringComparison.Ordinal)) - .ToList(); - if (matchingTabs.Count == 0) - { - await context.Response.WriteAsync($@" - <h1>Unable to find debuggable browser tab</h1> - <p> - The response from <code>{debuggerTabsListUrl}</code> does not include - any entry for <code>{targetTabUrl}</code>. - </p>"); - return; - } - else if (matchingTabs.Count > 1) - { - // TODO: Automatically disambiguate by adding a GUID to the page title - // when you press the debugger hotkey, include it in the querystring passed - // here, then remove it once the debugger connects. - await context.Response.WriteAsync($@" - <h1>Multiple matching tabs are open</h1> - <p> - There is more than one browser tab at <code>{targetTabUrl}</code>. - Close the ones you do not wish to debug, then refresh this page. - </p>"); - return; - } - - // Now we know uniquely which tab to debug, construct the URL to the debug - // page and redirect there - var tabToDebug = matchingTabs.Single(); - var underlyingV8Endpoint = tabToDebug.WebSocketDebuggerUrl; - var proxyEndpoint = $"{request.Host}{request.PathBase}/_framework/debug/ws-proxy?browser={WebUtility.UrlEncode(underlyingV8Endpoint)}"; - var devToolsUrlAbsolute = new Uri(debuggerHost + tabToDebug.DevtoolsFrontendUrl); - var wsParamName = request.IsHttps ? "wss" : "ws"; - var devToolsUrlWithProxy = $"{devToolsUrlAbsolute.Scheme}://{devToolsUrlAbsolute.Authority}{devToolsUrlAbsolute.AbsolutePath}?{wsParamName}={proxyEndpoint}"; - context.Response.Redirect(devToolsUrlWithProxy); - } - - private static string GetLaunchChromeInstructions(string appRootUrl) - { - var profilePath = Path.Combine(Path.GetTempPath(), "blazor-chrome-debug"); - var debuggerPort = GetDebuggerPort(); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return $@"<p>Press Win+R and enter the following:</p> - <p><strong><code>chrome --remote-debugging-port={debuggerPort} --user-data-dir=""{profilePath}"" {appRootUrl}</code></strong></p>"; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return $@"<p>In a terminal window execute the following:</p> - <p><strong><code>google-chrome --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {appRootUrl}</code></strong></p>"; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return $@"<p>Execute the following:</p> - <p><strong><code>open /Applications/Google\ Chrome.app --args --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {appRootUrl}</code></strong></p>"; - } - else - { - throw new InvalidOperationException("Unknown OS platform"); - } - } - - private static string GetLaunchEdgeInstructions(string appRootUrl) - { - var profilePath = Path.Combine(Path.GetTempPath(), "blazor-edge-debug"); - var debugggerPort = GetDebuggerPort(); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return $@"<p>Press Win+R and enter the following:</p> - <p><strong><code>msedge --remote-debugging-port={debugggerPort} --user-data-dir=""{profilePath}"" --no-first-run {appRootUrl}</code></strong></p>"; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - return $@"<p>In a terminal window execute the following:</p> - <p><strong><code>open /Applications/Microsoft\ Edge\ Dev.app --args --remote-debugging-port={debugggerPort} --user-data-dir={profilePath} {appRootUrl}</code></strong></p>"; - } - else - { - throw new InvalidOperationException("Unknown OS platform"); - } - } - - private static async Task<string> GetBrowserVersionInfoAsync() - { - using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }; - var debuggerHost = GetDebuggerHost(); - return await httpClient.GetStringAsync($"{debuggerHost}/json/version"); - } - - private static async Task<IEnumerable<BrowserTab>> GetOpenedBrowserTabs() - { - using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }; - var debuggerHost = GetDebuggerHost(); - var jsonResponse = await httpClient.GetStringAsync($"{debuggerHost}/json"); - return JsonSerializer.Deserialize<BrowserTab[]>(jsonResponse, JsonOptions); - } - - class BrowserTab - { - public string Id { get; set; } - public string Type { get; set; } - public string Url { get; set; } - public string Title { get; set; } - public string DevtoolsFrontendUrl { get; set; } - public string WebSocketDebuggerUrl { get; set; } - } - } -} diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/DebugStore.cs b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/DebugStore.cs deleted file mode 100644 index e1e9b7392ba854634cce785eb6fe9e7329a5af04..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/DebugStore.cs +++ /dev/null @@ -1,651 +0,0 @@ -using System; -using System.IO; -using System.Collections.Generic; -using Mono.Cecil; -using Mono.Cecil.Cil; -using System.Linq; -using Newtonsoft.Json.Linq; -using System.Net.Http; -using Mono.Cecil.Pdb; -using Newtonsoft.Json; -using System.Text.RegularExpressions; - -namespace WsProxy { - internal class BreakPointRequest { - public string Assembly { get; private set; } - public string File { get; private set; } - public int Line { get; private set; } - public int Column { get; private set; } - - public override string ToString () { - return $"BreakPointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}"; - } - - public static BreakPointRequest Parse (JObject args, DebugStore store) - { - if (args == null) - return null; - - var url = args? ["url"]?.Value<string> (); - if (url == null) { - var urlRegex = args?["urlRegex"].Value<string>(); - var sourceFile = store.GetFileByUrlRegex (urlRegex); - - url = sourceFile?.DotNetUrl; - } - - if (url != null && !url.StartsWith ("dotnet://", StringComparison.InvariantCulture)) { - var sourceFile = store.GetFileByUrl (url); - url = sourceFile?.DotNetUrl; - } - - if (url == null) - return null; - - var parts = ParseDocumentUrl (url); - if (parts.Assembly == null) - return null; - - var line = args? ["lineNumber"]?.Value<int> (); - var column = args? ["columnNumber"]?.Value<int> (); - if (line == null || column == null) - return null; - - return new BreakPointRequest () { - Assembly = parts.Assembly, - File = parts.DocumentPath, - Line = line.Value, - Column = column.Value - }; - } - - static (string Assembly, string DocumentPath) ParseDocumentUrl (string url) - { - if (Uri.TryCreate (url, UriKind.Absolute, out var docUri) && docUri.Scheme == "dotnet") { - return ( - docUri.Host, - docUri.PathAndQuery.Substring (1) - ); - } else { - return (null, null); - } - } - } - - - internal class VarInfo { - public VarInfo (VariableDebugInformation v) - { - this.Name = v.Name; - this.Index = v.Index; - } - - public VarInfo (ParameterDefinition p) - { - this.Name = p.Name; - this.Index = (p.Index + 1) * -1; - } - public string Name { get; private set; } - public int Index { get; private set; } - - - public override string ToString () - { - return $"(var-info [{Index}] '{Name}')"; - } - } - - - internal class CliLocation { - - private MethodInfo method; - private int offset; - - public CliLocation (MethodInfo method, int offset) - { - this.method = method; - this.offset = offset; - } - - public MethodInfo Method { get => method; } - public int Offset { get => offset; } - } - - - internal class SourceLocation { - SourceId id; - int line; - int column; - CliLocation cliLoc; - - public SourceLocation (SourceId id, int line, int column) - { - this.id = id; - this.line = line; - this.column = column; - } - - public SourceLocation (MethodInfo mi, SequencePoint sp) - { - this.id = mi.SourceId; - this.line = sp.StartLine - 1; - this.column = sp.StartColumn - 1; - this.cliLoc = new CliLocation (mi, sp.Offset); - } - - public SourceId Id { get => id; } - public int Line { get => line; } - public int Column { get => column; } - public CliLocation CliLocation => this.cliLoc; - - public override string ToString () - { - return $"{id}:{Line}:{Column}"; - } - - public static SourceLocation Parse (JObject obj) - { - if (obj == null) - return null; - - var id = SourceId.TryParse (obj ["scriptId"]?.Value<string> ()); - var line = obj ["lineNumber"]?.Value<int> (); - var column = obj ["columnNumber"]?.Value<int> (); - if (id == null || line == null || column == null) - return null; - - return new SourceLocation (id, line.Value, column.Value); - } - - internal JObject ToJObject () - { - return JObject.FromObject (new { - scriptId = id.ToString (), - lineNumber = line, - columnNumber = column - }); - } - - } - - internal class SourceId { - readonly int assembly, document; - - public int Assembly => assembly; - public int Document => document; - - internal SourceId (int assembly, int document) - { - this.assembly = assembly; - this.document = document; - } - - - public SourceId (string id) - { - id = id.Substring ("dotnet://".Length); - var sp = id.Split ('_'); - this.assembly = int.Parse (sp [0]); - this.document = int.Parse (sp [1]); - } - - public static SourceId TryParse (string id) - { - if (!id.StartsWith ("dotnet://", StringComparison.InvariantCulture)) - return null; - return new SourceId (id); - - } - public override string ToString () - { - return $"dotnet://{assembly}_{document}"; - } - - public override bool Equals (object obj) - { - if (obj == null) - return false; - SourceId that = obj as SourceId; - return that.assembly == this.assembly && that.document == this.document; - } - - public override int GetHashCode () - { - return this.assembly.GetHashCode () ^ this.document.GetHashCode (); - } - - public static bool operator == (SourceId a, SourceId b) - { - if ((object)a == null) - return (object)b == null; - return a.Equals (b); - } - - public static bool operator != (SourceId a, SourceId b) - { - return !a.Equals (b); - } - } - - internal class MethodInfo { - AssemblyInfo assembly; - internal MethodDefinition methodDef; - SourceFile source; - - public SourceId SourceId => source.SourceId; - - public string Name => methodDef.Name; - - public SourceLocation StartLocation { get; private set; } - public SourceLocation EndLocation { get; private set; } - public AssemblyInfo Assembly => assembly; - public int Token => (int)methodDef.MetadataToken.RID; - - public MethodInfo (AssemblyInfo assembly, MethodDefinition methodDef, SourceFile source) - { - this.assembly = assembly; - this.methodDef = methodDef; - this.source = source; - - var sps = methodDef.DebugInformation.SequencePoints; - if (sps != null && sps.Count > 0) { - StartLocation = new SourceLocation (this, sps [0]); - EndLocation = new SourceLocation (this, sps [sps.Count - 1]); - } - - } - - public SourceLocation GetLocationByIl (int pos) - { - SequencePoint prev = null; - foreach (var sp in methodDef.DebugInformation.SequencePoints) { - if (sp.Offset > pos) - break; - prev = sp; - } - - if (prev != null) - return new SourceLocation (this, prev); - - return null; - } - - public VarInfo [] GetLiveVarsAt (int offset) - { - var res = new List<VarInfo> (); - - res.AddRange (methodDef.Parameters.Select (p => new VarInfo (p))); - - res.AddRange (methodDef.DebugInformation.GetScopes () - .Where (s => s.Start.Offset <= offset && (s.End.IsEndOfMethod || s.End.Offset > offset)) - .SelectMany (s => s.Variables) - .Where (v => !v.IsDebuggerHidden) - .Select (v => new VarInfo (v))); - - - return res.ToArray (); - } - } - - internal class AssemblyInfo { - static int next_id; - ModuleDefinition image; - readonly int id; - Dictionary<int, MethodInfo> methods = new Dictionary<int, MethodInfo> (); - Dictionary<string, string> sourceLinkMappings = new Dictionary<string, string>(); - readonly List<SourceFile> sources = new List<SourceFile>(); - - public AssemblyInfo (byte[] assembly, byte[] pdb) - { - lock (typeof (AssemblyInfo)) { - this.id = ++next_id; - } - - try { - ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/); - if (pdb != null) { - rp.ReadSymbols = true; - rp.SymbolReaderProvider = new PortablePdbReaderProvider (); - rp.SymbolStream = new MemoryStream (pdb); - } - - rp.ReadingMode = ReadingMode.Immediate; - rp.InMemory = true; - - this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp); - } catch (BadImageFormatException ex) { - Console.WriteLine ($"Failed to read assembly as portable PDB: {ex.Message}"); - } - - if (this.image == null) { - ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/); - if (pdb != null) { - rp.ReadSymbols = true; - rp.SymbolReaderProvider = new NativePdbReaderProvider (); - rp.SymbolStream = new MemoryStream (pdb); - } - - rp.ReadingMode = ReadingMode.Immediate; - rp.InMemory = true; - - this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp); - } - - Populate (); - } - - public AssemblyInfo () - { - } - - void Populate () - { - ProcessSourceLink(); - - var d2s = new Dictionary<Document, SourceFile> (); - - Func<Document, SourceFile> get_src = (doc) => { - if (doc == null) - return null; - if (d2s.ContainsKey (doc)) - return d2s [doc]; - var src = new SourceFile (this, sources.Count, doc, GetSourceLinkUrl (doc.Url)); - sources.Add (src); - d2s [doc] = src; - return src; - }; - - foreach (var m in image.GetTypes().SelectMany(t => t.Methods)) { - Document first_doc = null; - foreach (var sp in m.DebugInformation.SequencePoints) { - if (first_doc == null && !sp.Document.Url.EndsWith (".g.cs")) { - first_doc = sp.Document; - } - // else if (first_doc != sp.Document) { - // //FIXME this is needed for (c)ctors in corlib - // throw new Exception ($"Cant handle multi-doc methods in {m}"); - //} - } - - if (first_doc == null) { - // all generated files - first_doc = m.DebugInformation.SequencePoints.FirstOrDefault ()?.Document; - } - - if (first_doc != null) { - var src = get_src (first_doc); - var mi = new MethodInfo (this, m, src); - int mt = (int)m.MetadataToken.RID; - this.methods [mt] = mi; - if (src != null) - src.AddMethod (mi); - } - } - } - - private void ProcessSourceLink () - { - var sourceLinkDebugInfo = image.CustomDebugInformations.FirstOrDefault (i => i.Kind == CustomDebugInformationKind.SourceLink); - - if (sourceLinkDebugInfo != null) { - var sourceLinkContent = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content; - - if (sourceLinkContent != null) { - var jObject = JObject.Parse (sourceLinkContent) ["documents"]; - sourceLinkMappings = JsonConvert.DeserializeObject<Dictionary<string, string>> (jObject.ToString ()); - } - } - } - - private Uri GetSourceLinkUrl (string document) - { - if (sourceLinkMappings.TryGetValue (document, out string url)) { - return new Uri (url); - } - - foreach (var sourceLinkDocument in sourceLinkMappings) { - string key = sourceLinkDocument.Key; - - if (Path.GetFileName (key) != "*") { - continue; - } - - var keyTrim = key.TrimEnd ('*'); - - if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase)) { - var docUrlPart = document.Replace (keyTrim, ""); - return new Uri (sourceLinkDocument.Value.TrimEnd ('*') + docUrlPart); - } - } - - return null; - } - - private string GetRelativePath (string relativeTo, string path) - { - var uri = new Uri (relativeTo, UriKind.RelativeOrAbsolute); - var rel = Uri.UnescapeDataString (uri.MakeRelativeUri (new Uri (path, UriKind.RelativeOrAbsolute)).ToString ()).Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - if (rel.Contains (Path.DirectorySeparatorChar.ToString ()) == false) { - rel = $".{ Path.DirectorySeparatorChar }{ rel }"; - } - return rel; - } - - public IEnumerable<SourceFile> Sources { - get { return this.sources; } - } - - public int Id => id; - public string Name => image.Name; - - public SourceFile GetDocById (int document) - { - return sources.FirstOrDefault (s => s.SourceId.Document == document); - } - - public MethodInfo GetMethodByToken (int token) - { - methods.TryGetValue (token, out var value); - return value; - } - } - - internal class SourceFile { - HashSet<MethodInfo> methods; - AssemblyInfo assembly; - int id; - Document doc; - - internal SourceFile (AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri) - { - this.methods = new HashSet<MethodInfo> (); - this.SourceLinkUri = sourceLinkUri; - this.assembly = assembly; - this.id = id; - this.doc = doc; - this.DebuggerFileName = doc.Url.Replace ("\\", "/").Replace (":", ""); - - this.SourceUri = new Uri ((Path.IsPathRooted (doc.Url) ? "file://" : "") + doc.Url, UriKind.RelativeOrAbsolute); - if (SourceUri.IsFile && File.Exists (SourceUri.LocalPath)) { - this.Url = this.SourceUri.ToString (); - } else { - this.Url = DotNetUrl; - } - - } - - internal void AddMethod (MethodInfo mi) - { - this.methods.Add (mi); - } - public string DebuggerFileName { get; } - public string Url { get; } - public string AssemblyName => assembly.Name; - public string DotNetUrl => $"dotnet://{assembly.Name}/{DebuggerFileName}"; - public string DocHashCode => "abcdee" + id; - public SourceId SourceId => new SourceId (assembly.Id, this.id); - public Uri SourceLinkUri { get; } - public Uri SourceUri { get; } - - public IEnumerable<MethodInfo> Methods => this.methods; - } - - internal class DebugStore { - List<AssemblyInfo> assemblies = new List<AssemblyInfo> (); - - public DebugStore (string [] loaded_files) - { - bool MatchPdb (string asm, string pdb) - { - return Path.ChangeExtension (asm, "pdb") == pdb; - } - - var asm_files = new List<string> (); - var pdb_files = new List<string> (); - foreach (var f in loaded_files) { - var file_name = f; - if (file_name.EndsWith (".pdb", StringComparison.OrdinalIgnoreCase)) - pdb_files.Add (file_name); - else - asm_files.Add (file_name); - } - - //FIXME make this parallel - foreach (var p in asm_files) { - try { - var pdb = pdb_files.FirstOrDefault (n => MatchPdb (p, n)); - HttpClient h = new HttpClient (); - var assembly_bytes = h.GetByteArrayAsync (p).Result; - byte [] pdb_bytes = null; - if (pdb != null) - pdb_bytes = h.GetByteArrayAsync (pdb).Result; - - this.assemblies.Add (new AssemblyInfo (assembly_bytes, pdb_bytes)); - } catch (Exception e) { - Console.WriteLine ($"Failed to read {p} ({e.Message})"); - } - } - } - - public IEnumerable<SourceFile> AllSources () - { - foreach (var a in assemblies) { - foreach (var s in a.Sources) - yield return s; - } - } - - public SourceFile GetFileById (SourceId id) - { - return AllSources ().FirstOrDefault (f => f.SourceId.Equals (id)); - } - - public AssemblyInfo GetAssemblyByName (string name) - { - return assemblies.FirstOrDefault (a => a.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase)); - } - - /* - V8 uses zero based indexing for both line and column. - PPDBs uses one based indexing for both line and column. - */ - static bool Match (SequencePoint sp, SourceLocation start, SourceLocation end) - { - var spStart = (Line: sp.StartLine - 1, Column: sp.StartColumn - 1); - var spEnd = (Line: sp.EndLine - 1, Column: sp.EndColumn - 1); - - if (start.Line > spStart.Line) - return false; - if (start.Column > spStart.Column && start.Line == sp.StartLine) - return false; - - if (end.Line < spEnd.Line) - return false; - - if (end.Column < spEnd.Column && end.Line == spEnd.Line) - return false; - - return true; - } - - public List<SourceLocation> FindPossibleBreakpoints (SourceLocation start, SourceLocation end) - { - //XXX FIXME no idea what todo with locations on different files - if (start.Id != end.Id) - return null; - var src_id = start.Id; - - var doc = GetFileById (src_id); - - var res = new List<SourceLocation> (); - if (doc == null) { - //FIXME we need to write up logging here - Console.WriteLine ($"Could not find document {src_id}"); - return res; - } - - foreach (var m in doc.Methods) { - foreach (var sp in m.methodDef.DebugInformation.SequencePoints) { - if (Match (sp, start, end)) - res.Add (new SourceLocation (m, sp)); - } - } - return res; - } - - /* - V8 uses zero based indexing for both line and column. - PPDBs uses one based indexing for both line and column. - */ - static bool Match (SequencePoint sp, int line, int column) - { - var bp = (line: line + 1, column: column + 1); - - if (sp.StartLine > bp.line || sp.EndLine < bp.line) - return false; - - //Chrome sends a zero column even if getPossibleBreakpoints say something else - if (column == 0) - return true; - - if (sp.StartColumn > bp.column && sp.StartLine == bp.line) - return false; - - if (sp.EndColumn < bp.column && sp.EndLine == bp.line) - return false; - - return true; - } - - public SourceLocation FindBestBreakpoint (BreakPointRequest req) - { - var asm = assemblies.FirstOrDefault (a => a.Name.Equals (req.Assembly, StringComparison.OrdinalIgnoreCase)); - var src = asm?.Sources?.FirstOrDefault (s => s.DebuggerFileName.Equals (req.File, StringComparison.OrdinalIgnoreCase)); - - if (src == null) - return null; - - foreach (var m in src.Methods) { - foreach (var sp in m.methodDef.DebugInformation.SequencePoints) { - //FIXME handle multi doc methods - if (Match (sp, req.Line, req.Column)) - return new SourceLocation (m, sp); - } - } - - return null; - } - - public string ToUrl (SourceLocation location) - => location != null ? GetFileById (location.Id).Url : ""; - - public SourceFile GetFileByUrlRegex (string urlRegex) - { - var regex = new Regex (urlRegex); - return AllSources ().FirstOrDefault (file => regex.IsMatch (file.Url.ToString())); - } - - public SourceFile GetFileByUrl (string url) - => AllSources ().FirstOrDefault (file => file.Url.ToString() == url); - } -} diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs deleted file mode 100644 index eb4cf65b50a41b54e41375e0939b4b104a70ca29..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs +++ /dev/null @@ -1,792 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Newtonsoft.Json.Linq; - -using System.Net.WebSockets; -using System.Threading; -using System.IO; -using System.Text; -using System.Collections.Generic; -using System.Net; - -namespace WsProxy { - - internal class MonoCommands { - public const string GET_CALL_STACK = "MONO.mono_wasm_get_call_stack()"; - public const string IS_RUNTIME_READY_VAR = "MONO.mono_wasm_runtime_is_ready"; - public const string START_SINGLE_STEPPING = "MONO.mono_wasm_start_single_stepping({0})"; - public const string GET_SCOPE_VARIABLES = "MONO.mono_wasm_get_variables({0}, [ {1} ])"; - public const string SET_BREAK_POINT = "MONO.mono_wasm_set_breakpoint(\"{0}\", {1}, {2})"; - public const string REMOVE_BREAK_POINT = "MONO.mono_wasm_remove_breakpoint({0})"; - public const string GET_LOADED_FILES = "MONO.mono_wasm_get_loaded_files()"; - public const string CLEAR_ALL_BREAKPOINTS = "MONO.mono_wasm_clear_all_breakpoints()"; - public const string GET_OBJECT_PROPERTIES = "MONO.mono_wasm_get_object_properties({0})"; - public const string GET_ARRAY_VALUES = "MONO.mono_wasm_get_array_values({0})"; - } - - internal enum MonoErrorCodes { - BpNotFound = 100000, - } - - - internal class MonoConstants { - public const string RUNTIME_IS_READY = "mono_wasm_runtime_ready"; - } - class Frame { - public Frame (MethodInfo method, SourceLocation location, int id) - { - this.Method = method; - this.Location = location; - this.Id = id; - } - - public MethodInfo Method { get; private set; } - public SourceLocation Location { get; private set; } - public int Id { get; private set; } - } - - - class Breakpoint { - public SourceLocation Location { get; private set; } - public int LocalId { get; private set; } - public int RemoteId { get; set; } - public BreakPointState State { get; set; } - - public Breakpoint (SourceLocation loc, int localId, BreakPointState state) - { - this.Location = loc; - this.LocalId = localId; - this.State = state; - } - } - - enum BreakPointState { - Active, - Disabled, - Pending - } - - enum StepKind { - Into, - Out, - Over - } - - internal class MonoProxy : WsProxy { - DebugStore store; - List<Breakpoint> breakpoints = new List<Breakpoint> (); - List<Frame> current_callstack; - bool runtime_ready; - int local_breakpoint_id; - int ctx_id; - JObject aux_ctx_data; - - public MonoProxy () { } - - protected override async Task<bool> AcceptEvent (string method, JObject args, CancellationToken token) - { - switch (method) { - case "Runtime.executionContextCreated": { - var ctx = args? ["context"]; - var aux_data = ctx? ["auxData"] as JObject; - if (aux_data != null) { - var is_default = aux_data ["isDefault"]?.Value<bool> (); - if (is_default == true) { - var ctx_id = ctx ["id"].Value<int> (); - await OnDefaultContext (ctx_id, aux_data, token); - } - } - break; - } - case "Debugger.paused": { - //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack - var top_func = args? ["callFrames"]? [0]? ["functionName"]?.Value<string> (); - if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp") { - await OnBreakPointHit (args, token); - return true; - } - if (top_func == MonoConstants.RUNTIME_IS_READY) { - await OnRuntimeReady (token); - return true; - } - break; - } - case "Debugger.scriptParsed":{ - if (args?["url"]?.Value<string> ()?.StartsWith ("wasm://") == true) { - // Console.WriteLine ("ignoring wasm event"); - return true; - } - break; - } - } - - return false; - } - - - protected override async Task<bool> AcceptCommand (int id, string method, JObject args, CancellationToken token) - { - switch (method) { - case "Debugger.getScriptSource": { - var script_id = args? ["scriptId"]?.Value<string> (); - if (script_id.StartsWith ("dotnet://", StringComparison.InvariantCultureIgnoreCase)) { - await OnGetScriptSource (id, script_id, token); - return true; - } - - break; - } - case "Runtime.compileScript": { - var exp = args? ["expression"]?.Value<string> (); - if (exp.StartsWith ("//dotnet:", StringComparison.InvariantCultureIgnoreCase)) { - OnCompileDotnetScript (id, token); - return true; - } - break; - } - - case "Debugger.getPossibleBreakpoints": { - var start = SourceLocation.Parse (args? ["start"] as JObject); - //FIXME support variant where restrictToFunction=true and end is omitted - var end = SourceLocation.Parse (args? ["end"] as JObject); - if (start != null && end != null) - return GetPossibleBreakpoints (id, start, end, token); - break; - } - - case "Debugger.setBreakpointByUrl": { - Info ($"BP req {args}"); - var bp_req = BreakPointRequest.Parse (args, store); - if (bp_req != null) { - await SetBreakPoint (id, bp_req, token); - return true; - } - break; - } - case "Debugger.removeBreakpoint": { - return await RemoveBreakpoint (id, args, token); - } - - case "Debugger.resume": { - await OnResume (token); - break; - } - - case "Debugger.stepInto": { - if (this.current_callstack != null) { - await Step (id, StepKind.Into, token); - return true; - } - break; - } - - case "Debugger.stepOut": { - if (this.current_callstack != null) { - await Step (id, StepKind.Out, token); - return true; - } - break; - } - - case "Debugger.stepOver": { - if (this.current_callstack != null) { - await Step (id, StepKind.Over, token); - return true; - } - break; - } - - case "Runtime.getProperties": { - var objId = args? ["objectId"]?.Value<string> (); - if (objId.StartsWith ("dotnet:scope:", StringComparison.InvariantCulture)) { - await GetScopeProperties (id, int.Parse (objId.Substring ("dotnet:scope:".Length)), token); - return true; - } - if (objId.StartsWith("dotnet:", StringComparison.InvariantCulture)) - { - if (objId.StartsWith("dotnet:object:", StringComparison.InvariantCulture)) - await GetDetails(id, int.Parse(objId.Substring("dotnet:object:".Length)), token, MonoCommands.GET_OBJECT_PROPERTIES); - if (objId.StartsWith("dotnet:array:", StringComparison.InvariantCulture)) - await GetDetails(id, int.Parse(objId.Substring("dotnet:array:".Length)), token, MonoCommands.GET_ARRAY_VALUES); - return true; - } - break; - } - } - - return false; - } - - async Task OnRuntimeReady (CancellationToken token) - { - Info ("RUNTIME READY, PARTY TIME"); - await RuntimeReady (token); - await SendCommand ("Debugger.resume", new JObject (), token); - SendEvent ("Mono.runtimeReady", new JObject (), token); - } - - async Task OnBreakPointHit (JObject args, CancellationToken token) - { - //FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime - var o = JObject.FromObject (new { - expression = MonoCommands.GET_CALL_STACK, - objectGroup = "mono_debugger", - includeCommandLineAPI = false, - silent = false, - returnByValue = true - }); - - var orig_callframes = args? ["callFrames"]?.Values<JObject> (); - var res = await SendCommand ("Runtime.evaluate", o, token); - - if (res.IsErr) { - //Give up and send the original call stack - SendEvent ("Debugger.paused", args, token); - return; - } - - //step one, figure out where did we hit - var res_value = res.Value? ["result"]? ["value"]; - if (res_value == null || res_value is JValue) { - //Give up and send the original call stack - SendEvent ("Debugger.paused", args, token); - return; - } - - Debug ($"call stack (err is {res.Error} value is:\n{res.Value}"); - var bp_id = res_value? ["breakpoint_id"]?.Value<int> (); - Debug ($"We just hit bp {bp_id}"); - if (!bp_id.HasValue) { - //Give up and send the original call stack - SendEvent ("Debugger.paused", args, token); - return; - } - var bp = this.breakpoints.FirstOrDefault (b => b.RemoteId == bp_id.Value); - - var src = bp == null ? null : store.GetFileById (bp.Location.Id); - - var callFrames = new List<JObject> (); - foreach (var frame in orig_callframes) { - var function_name = frame ["functionName"]?.Value<string> (); - var url = frame ["url"]?.Value<string> (); - if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name) { - var frames = new List<Frame> (); - int frame_id = 0; - var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values<JObject> (); - - foreach (var mono_frame in the_mono_frames) { - var il_pos = mono_frame ["il_pos"].Value<int> (); - var method_token = mono_frame ["method_token"].Value<int> (); - var assembly_name = mono_frame ["assembly_name"].Value<string> (); - - var asm = store.GetAssemblyByName (assembly_name); - if (asm == null) { - Info ($"Unable to find assembly: {assembly_name}"); - continue; - } - - var method = asm.GetMethodByToken (method_token); - - if (method == null) { - Info ($"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}"); - continue; - } - - var location = method?.GetLocationByIl (il_pos); - - // When hitting a breakpoint on the "IncrementCount" method in the standard - // Blazor project template, one of the stack frames is inside mscorlib.dll - // and we get location==null for it. It will trigger a NullReferenceException - // if we don't skip over that stack frame. - if (location == null) { - continue; - } - - Info ($"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}"); - Info ($"\tmethod {method.Name} location: {location}"); - frames.Add (new Frame (method, location, frame_id)); - - callFrames.Add (JObject.FromObject (new { - functionName = method.Name, - callFrameId = $"dotnet:scope:{frame_id}", - functionLocation = method.StartLocation.ToJObject (), - - location = location.ToJObject (), - - url = store.ToUrl (location), - - scopeChain = new [] { - new { - type = "local", - @object = new { - @type = "object", - className = "Object", - description = "Object", - objectId = $"dotnet:scope:{frame_id}", - }, - name = method.Name, - startLocation = method.StartLocation.ToJObject (), - endLocation = method.EndLocation.ToJObject (), - }} - })); - - ++frame_id; - this.current_callstack = frames; - - } - } else if (!(function_name.StartsWith ("wasm-function", StringComparison.InvariantCulture) - || url.StartsWith ("wasm://wasm/", StringComparison.InvariantCulture))) { - callFrames.Add (frame); - } - } - - var bp_list = new string [bp == null ? 0 : 1]; - if (bp != null) - bp_list [0] = $"dotnet:{bp.LocalId}"; - - o = JObject.FromObject (new { - callFrames = callFrames, - reason = "other", //other means breakpoint - hitBreakpoints = bp_list, - }); - - SendEvent ("Debugger.paused", o, token); - } - - async Task OnDefaultContext (int ctx_id, JObject aux_data, CancellationToken token) - { - Debug ("Default context created, clearing state and sending events"); - - //reset all bps - foreach (var b in this.breakpoints){ - b.State = BreakPointState.Pending; - } - this.runtime_ready = false; - - var o = JObject.FromObject (new { - expression = MonoCommands.IS_RUNTIME_READY_VAR, - objectGroup = "mono_debugger", - includeCommandLineAPI = false, - silent = false, - returnByValue = true - }); - this.ctx_id = ctx_id; - this.aux_ctx_data = aux_data; - - Debug ("checking if the runtime is ready"); - var res = await SendCommand ("Runtime.evaluate", o, token); - var is_ready = res.Value? ["result"]? ["value"]?.Value<bool> (); - //Debug ($"\t{is_ready}"); - if (is_ready.HasValue && is_ready.Value == true) { - Debug ("RUNTIME LOOK READY. GO TIME!"); - await OnRuntimeReady (token); - } - } - - - async Task OnResume (CancellationToken token) - { - //discard frames - this.current_callstack = null; - await Task.CompletedTask; - } - - async Task Step (int msg_id, StepKind kind, CancellationToken token) - { - - var o = JObject.FromObject (new { - expression = string.Format (MonoCommands.START_SINGLE_STEPPING, (int)kind), - objectGroup = "mono_debugger", - includeCommandLineAPI = false, - silent = false, - returnByValue = true, - }); - - var res = await SendCommand ("Runtime.evaluate", o, token); - - SendResponse (msg_id, Result.Ok (new JObject ()), token); - - this.current_callstack = null; - - await SendCommand ("Debugger.resume", new JObject (), token); - } - - async Task GetDetails(int msg_id, int object_id, CancellationToken token, string command) - { - var o = JObject.FromObject(new - { - expression = string.Format(command, object_id), - objectGroup = "mono_debugger", - includeCommandLineAPI = false, - silent = false, - returnByValue = true, - }); - - var res = await SendCommand("Runtime.evaluate", o, token); - - //if we fail we just buble that to the IDE (and let it panic over it) - if (res.IsErr) - { - SendResponse(msg_id, res, token); - return; - } - - try { - var values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray() ?? Array.Empty<JObject>(); - var var_list = new List<JObject>(); - - // Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously - // results in a "Memory access out of bounds", causing 'values' to be null, - // so skip returning variable values in that case. - for (int i = 0; i < values.Length; i+=2) - { - string fieldName = (string)values[i]["name"]; - if (fieldName.Contains("k__BackingField")){ - fieldName = fieldName.Replace("k__BackingField", ""); - fieldName = fieldName.Replace("<", ""); - fieldName = fieldName.Replace(">", ""); - } - var value = values [i + 1]? ["value"]; - if (((string)value ["description"]) == null) - value ["description"] = value ["value"]?.ToString (); - - var_list.Add(JObject.FromObject(new { - name = fieldName, - value - })); - - } - o = JObject.FromObject(new - { - result = var_list - }); - } catch (Exception) { - Debug ($"failed to parse {res.Value}"); - } - SendResponse(msg_id, Result.Ok(o), token); - } - - - async Task GetScopeProperties (int msg_id, int scope_id, CancellationToken token) - { - var scope = this.current_callstack.FirstOrDefault (s => s.Id == scope_id); - var vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset); - - - var var_ids = string.Join (",", vars.Select (v => v.Index)); - - var o = JObject.FromObject (new { - expression = string.Format (MonoCommands.GET_SCOPE_VARIABLES, scope.Id, var_ids), - objectGroup = "mono_debugger", - includeCommandLineAPI = false, - silent = false, - returnByValue = true, - }); - - var res = await SendCommand ("Runtime.evaluate", o, token); - - //if we fail we just buble that to the IDE (and let it panic over it) - if (res.IsErr) { - SendResponse (msg_id, res, token); - return; - } - - try { - var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray (); - - var var_list = new List<JObject> (); - int i = 0; - // Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously - // results in a "Memory access out of bounds", causing 'values' to be null, - // so skip returning variable values in that case. - while (values != null && i < vars.Length && i < values.Length) { - var value = values [i] ["value"]; - if (((string)value ["description"]) == null) - value ["description"] = value ["value"]?.ToString (); - - var_list.Add (JObject.FromObject (new { - name = vars [i].Name, - value - })); - i++; - } - //Async methods are special in the way that local variables can be lifted to generated class fields - //value of "this" comes here either - while (i < values.Length) { - String name = values [i] ["name"].ToString (); - - if (name.IndexOf (">", StringComparison.Ordinal) > 0) - name = name.Substring (1, name.IndexOf (">", StringComparison.Ordinal) - 1); - - var value = values [i + 1] ["value"]; - if (((string)value ["description"]) == null) - value ["description"] = value ["value"]?.ToString (); - - var_list.Add (JObject.FromObject (new { - name, - value - })); - i = i + 2; - } - o = JObject.FromObject (new { - result = var_list - }); - SendResponse (msg_id, Result.Ok (o), token); - } - catch (Exception) { - SendResponse (msg_id, res, token); - } - } - - async Task<Result> EnableBreakPoint (Breakpoint bp, CancellationToken token) - { - var asm_name = bp.Location.CliLocation.Method.Assembly.Name; - var method_token = bp.Location.CliLocation.Method.Token; - var il_offset = bp.Location.CliLocation.Offset; - - var o = JObject.FromObject (new { - expression = string.Format (MonoCommands.SET_BREAK_POINT, asm_name, method_token, il_offset), - objectGroup = "mono_debugger", - includeCommandLineAPI = false, - silent = false, - returnByValue = true, - }); - - var res = await SendCommand ("Runtime.evaluate", o, token); - var ret_code = res.Value? ["result"]? ["value"]?.Value<int> (); - - if (ret_code.HasValue) { - bp.RemoteId = ret_code.Value; - bp.State = BreakPointState.Active; - //Debug ($"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}"); - } - - return res; - } - - async Task RuntimeReady (CancellationToken token) - { - - var o = JObject.FromObject (new { - expression = MonoCommands.GET_LOADED_FILES, - objectGroup = "mono_debugger", - includeCommandLineAPI = false, - silent = false, - returnByValue = true, - }); - var loaded_pdbs = await SendCommand ("Runtime.evaluate", o, token); - var the_value = loaded_pdbs.Value? ["result"]? ["value"]; - var the_pdbs = the_value?.ToObject<string[]> (); - this.store = new DebugStore (the_pdbs); - - foreach (var s in store.AllSources ()) { - var ok = JObject.FromObject (new { - scriptId = s.SourceId.ToString (), - url = s.Url, - executionContextId = this.ctx_id, - hash = s.DocHashCode, - executionContextAuxData = this.aux_ctx_data, - dotNetUrl = s.DotNetUrl - }); - //Debug ($"\tsending {s.Url}"); - SendEvent ("Debugger.scriptParsed", ok, token); - } - - o = JObject.FromObject (new { - expression = MonoCommands.CLEAR_ALL_BREAKPOINTS, - objectGroup = "mono_debugger", - includeCommandLineAPI = false, - silent = false, - returnByValue = true, - }); - - var clear_result = await SendCommand ("Runtime.evaluate", o, token); - if (clear_result.IsErr) { - Debug ($"Failed to clear breakpoints due to {clear_result}"); - } - - - runtime_ready = true; - - foreach (var bp in breakpoints) { - if (bp.State != BreakPointState.Pending) - continue; - var res = await EnableBreakPoint (bp, token); - var ret_code = res.Value? ["result"]? ["value"]?.Value<int> (); - - //if we fail we just buble that to the IDE (and let it panic over it) - if (!ret_code.HasValue) { - //FIXME figure out how to inform the IDE of that. - Info ($"FAILED TO ENABLE BP {bp.LocalId}"); - bp.State = BreakPointState.Disabled; - } - } - } - - async Task<bool> RemoveBreakpoint(int msg_id, JObject args, CancellationToken token) { - var bpid = args? ["breakpointId"]?.Value<string> (); - if (bpid?.StartsWith ("dotnet:") != true) - return false; - - var the_id = int.Parse (bpid.Substring ("dotnet:".Length)); - - var bp = breakpoints.FirstOrDefault (b => b.LocalId == the_id); - if (bp == null) { - Info ($"Could not find dotnet bp with id {the_id}"); - return false; - } - - breakpoints.Remove (bp); - //FIXME verify result (and log?) - var res = await RemoveBreakPoint (bp, token); - - return true; - } - - - async Task<Result> RemoveBreakPoint (Breakpoint bp, CancellationToken token) - { - var o = JObject.FromObject (new { - expression = string.Format (MonoCommands.REMOVE_BREAK_POINT, bp.RemoteId), - objectGroup = "mono_debugger", - includeCommandLineAPI = false, - silent = false, - returnByValue = true, - }); - - var res = await SendCommand ("Runtime.evaluate", o, token); - var ret_code = res.Value? ["result"]? ["value"]?.Value<int> (); - - if (ret_code.HasValue) { - bp.RemoteId = -1; - bp.State = BreakPointState.Disabled; - } - - return res; - } - - async Task SetBreakPoint (int msg_id, BreakPointRequest req, CancellationToken token) - { - var bp_loc = store.FindBestBreakpoint (req); - Info ($"BP request for '{req}' runtime ready {runtime_ready} location '{bp_loc}'"); - if (bp_loc == null) { - - Info ($"Could not resolve breakpoint request: {req}"); - SendResponse (msg_id, Result.Err(JObject.FromObject (new { - code = (int)MonoErrorCodes.BpNotFound, - message = $"C# Breakpoint at {req} not found." - })), token); - return; - } - - Breakpoint bp = null; - if (!runtime_ready) { - bp = new Breakpoint (bp_loc, local_breakpoint_id++, BreakPointState.Pending); - } else { - bp = new Breakpoint (bp_loc, local_breakpoint_id++, BreakPointState.Disabled); - - var res = await EnableBreakPoint (bp, token); - var ret_code = res.Value? ["result"]? ["value"]?.Value<int> (); - - //if we fail we just buble that to the IDE (and let it panic over it) - if (!ret_code.HasValue) { - SendResponse (msg_id, res, token); - return; - } - } - - var locations = new List<JObject> (); - - locations.Add (JObject.FromObject (new { - scriptId = bp_loc.Id.ToString (), - lineNumber = bp_loc.Line, - columnNumber = bp_loc.Column - })); - - breakpoints.Add (bp); - - var ok = JObject.FromObject (new { - breakpointId = $"dotnet:{bp.LocalId}", - locations = locations, - }); - - SendResponse (msg_id, Result.Ok (ok), token); - } - - bool GetPossibleBreakpoints (int msg_id, SourceLocation start, SourceLocation end, CancellationToken token) - { - var bps = store.FindPossibleBreakpoints (start, end); - if (bps == null) - return false; - - var loc = new List<JObject> (); - foreach (var b in bps) { - loc.Add (b.ToJObject ()); - } - - var o = JObject.FromObject (new { - locations = loc - }); - - SendResponse (msg_id, Result.Ok (o), token); - - return true; - } - - void OnCompileDotnetScript (int msg_id, CancellationToken token) - { - var o = JObject.FromObject (new { }); - - SendResponse (msg_id, Result.Ok (o), token); - - } - - async Task OnGetScriptSource (int msg_id, string script_id, CancellationToken token) - { - var id = new SourceId (script_id); - var src_file = store.GetFileById (id); - - var res = new StringWriter (); - //res.WriteLine ($"//{id}"); - - try { - var uri = new Uri (src_file.Url); - if (uri.IsFile && File.Exists(uri.LocalPath)) { - using (var f = new StreamReader (File.Open (src_file.SourceUri.LocalPath, FileMode.Open))) { - await res.WriteAsync (await f.ReadToEndAsync ()); - } - - var o = JObject.FromObject (new { - scriptSource = res.ToString () - }); - - SendResponse (msg_id, Result.Ok (o), token); - } else if(src_file.SourceLinkUri != null) { - var doc = await new WebClient ().DownloadStringTaskAsync (src_file.SourceLinkUri); - await res.WriteAsync (doc); - - var o = JObject.FromObject (new { - scriptSource = res.ToString () - }); - - SendResponse (msg_id, Result.Ok (o), token); - } else { - var o = JObject.FromObject (new { - scriptSource = $"// Unable to find document {src_file.SourceUri}" - }); - - SendResponse (msg_id, Result.Ok (o), token); - } - } catch (Exception e) { - var o = JObject.FromObject (new { - scriptSource = $"// Unable to read document ({e.Message})\n" + - $"Local path: {src_file?.SourceUri}\n" + - $"SourceLink path: {src_file?.SourceLinkUri}\n" - }); - - SendResponse (msg_id, Result.Ok (o), token); - } - } - } -} diff --git a/src/Components/Blazor/Templates/.gitignore b/src/Components/Blazor/Templates/.gitignore deleted file mode 100644 index 6216d1eae654ec351d31643e1906c7ecbe678784..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Templates/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# We only track the .template.config.src items in source control -# The .template.config files are generated on build -src/content/**/.template.config/ diff --git a/src/Components/Blazor/Templates/src/Microsoft.AspNetCore.Blazor.Templates.csproj b/src/Components/Blazor/Templates/src/Microsoft.AspNetCore.Blazor.Templates.csproj deleted file mode 100644 index 55364eee45fc970d059b0e1ae6e1bbaa24963430..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Templates/src/Microsoft.AspNetCore.Blazor.Templates.csproj +++ /dev/null @@ -1,64 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <TargetFramework>netstandard2.0</TargetFramework> - <NuspecFile>Microsoft.AspNetCore.Blazor.Templates.nuspec</NuspecFile> - <IsShippingPackage>false</IsShippingPackage> - <EnableDefaultItems>False</EnableDefaultItems> - <GenerateAssemblyInfo>False</GenerateAssemblyInfo> - <IncludeBuildOutput>False</IncludeBuildOutput> - <CopyBuildOutputToOutputDirectory>false</CopyBuildOutputToOutputDirectory> - <DebugType>none</DebugType> - <GenerateDocumentationFile>false</GenerateDocumentationFile> - <NoWarn>$(NoWarn);2008</NoWarn> - <Description>Templates for ASP.NET Core Blazor projects.</Description> - <PackageTags>aspnet;templates;blazor;spa</PackageTags> - <IsProjectReferenceProvider>false</IsProjectReferenceProvider> - </PropertyGroup> - - <ItemGroup> - <UpToDateCheckInput Include="content\**\.template.config.src\**\*.*" /> - </ItemGroup> - - <Target Name="PrepareFileLists" AfterTargets="PrepareForBuild"> - <ItemGroup> - <_TemplateConfigMainFile Include="content\**\.template.config.src\template.json" /> - <_TemplateConfigDir Include="@(_TemplateConfigMainFile->'$([System.IO.Path]::GetDirectoryName('%(_TemplateConfigMainFile.FullPath)'))')" /> - <_TemplateConfigFileToCopy Include="%(_TemplateConfigDir.Identity)\**\*.*"> - <DestDir>$([System.IO.Path]::GetDirectoryName('%(_TemplateConfigDir.Identity)'))\.template.config\</DestDir> - </_TemplateConfigFileToCopy> - </ItemGroup> - </Target> - - <Target - Name="TransformTemplateConfigs" - BeforeTargets="CoreBuild" - DependsOnTargets="SetTemplateJsonSymbolReplacements" - Inputs="@(_TemplateConfigFileToCopy)" - Outputs="@(_TemplateConfigFileToCopy->'%(DestDir)%(FileName)%(Extension)')"> - - <!-- - For each template, copy its '.template.config.src' directory to '.template.config', - removing any earlier output at that location - --> - <RemoveDir Directories="%(_TemplateConfigFileToCopy.DestDir)" /> - <Copy SourceFiles="%(_TemplateConfigFileToCopy.Identity)" DestinationFolder="%(_TemplateConfigFileToCopy.DestDir)" /> - - <!-- - Modify the .json files in the .template.config dirs to stamp MSBuild properties into them - --> - <ItemGroup> - <GeneratedContent Include="@(_TemplateConfigFileToCopy->WithMetadataValue('Extension','.json'))"> - <OutputPath>%(DestDir)%(RecursiveDir)%(Filename)%(Extension)</OutputPath> - <Properties>$(GeneratedContentProperties)</Properties> - </GeneratedContent> - </ItemGroup> - <GenerateFileFromTemplate - TemplateFile="%(GeneratedContent.Identity)" - Properties="%(GeneratedContent.Properties)" - OutputPath="%(GeneratedContent.OutputPath)"> - - <Output TaskParameter="OutputPath" ItemName="FileWrites" /> - <Output TaskParameter="OutputPath" ItemName="Content" /> - </GenerateFileFromTemplate> - </Target> -</Project> diff --git a/src/Components/Blazor/Validation/src/ComparePropertyAttribute.cs b/src/Components/Blazor/Validation/src/ComparePropertyAttribute.cs deleted file mode 100644 index 3f74ce647f3758182db338d97e8b6d9fcff82276..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Validation/src/ComparePropertyAttribute.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace System.ComponentModel.DataAnnotations -{ - /// <summary> - /// A <see cref="ValidationAttribute"/> that compares two properties - /// </summary> - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] - public sealed class ComparePropertyAttribute : CompareAttribute - { - /// <summary> - /// Initializes a new instance of <see cref="BlazorCompareAttribute"/>. - /// </summary> - /// <param name="otherProperty">The property to compare with the current property.</param> - public ComparePropertyAttribute(string otherProperty) - : base(otherProperty) - { - } - - /// <inheritdoc /> - protected override ValidationResult IsValid(object value, ValidationContext validationContext) - { - var validationResult = base.IsValid(value, validationContext); - if (validationResult == ValidationResult.Success) - { - return validationResult; - } - - return new ValidationResult(validationResult.ErrorMessage, new[] { validationContext.MemberName }); - } - } -} - diff --git a/src/Components/Blazor/Validation/src/Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj b/src/Components/Blazor/Validation/src/Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj deleted file mode 100644 index 53cc678edbb0b169038dcc48ad88fb3e42268d4c..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Validation/src/Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj +++ /dev/null @@ -1,18 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>netstandard2.1</TargetFramework> - <Description>Provides experimental support for validation using DataAnnotations.</Description> - <IsShippingPackage>false</IsShippingPackage> - <HasReferenceAssembly>false</HasReferenceAssembly> - </PropertyGroup> - - <ItemGroup> - <Reference Include="Microsoft.AspNetCore.Components.Forms" /> - </ItemGroup> - - <ItemGroup> - <InternalsVisibleTo Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests" /> - </ItemGroup> - -</Project> diff --git a/src/Components/Blazor/Validation/src/ObjectGraphDataAnnotationsValidator.cs b/src/Components/Blazor/Validation/src/ObjectGraphDataAnnotationsValidator.cs deleted file mode 100644 index df1971e0a2008286fb006fae15844a9499ddd1c1..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Validation/src/ObjectGraphDataAnnotationsValidator.cs +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; - -namespace Microsoft.AspNetCore.Components.Forms -{ - public class ObjectGraphDataAnnotationsValidator : ComponentBase - { - private static readonly object ValidationContextValidatorKey = new object(); - private static readonly object ValidatedObjectsKey = new object(); - private ValidationMessageStore _validationMessageStore; - - [CascadingParameter] - internal EditContext EditContext { get; set; } - - protected override void OnInitialized() - { - _validationMessageStore = new ValidationMessageStore(EditContext); - - // Perform object-level validation (starting from the root model) on request - EditContext.OnValidationRequested += (sender, eventArgs) => - { - _validationMessageStore.Clear(); - ValidateObject(EditContext.Model, new HashSet<object>()); - EditContext.NotifyValidationStateChanged(); - }; - - // Perform per-field validation on each field edit - EditContext.OnFieldChanged += (sender, eventArgs) => - ValidateField(EditContext, _validationMessageStore, eventArgs.FieldIdentifier); - } - - internal void ValidateObject(object value, HashSet<object> visited) - { - if (value is null) - { - return; - } - - if (!visited.Add(value)) - { - // Already visited this object. - return; - } - - if (value is IEnumerable<object> enumerable) - { - var index = 0; - foreach (var item in enumerable) - { - ValidateObject(item, visited); - index++; - } - - return; - } - - var validationResults = new List<ValidationResult>(); - ValidateObject(value, visited, validationResults); - - // Transfer results to the ValidationMessageStore - foreach (var validationResult in validationResults) - { - if (!validationResult.MemberNames.Any()) - { - _validationMessageStore.Add(new FieldIdentifier(value, string.Empty), validationResult.ErrorMessage); - continue; - } - - foreach (var memberName in validationResult.MemberNames) - { - var fieldIdentifier = new FieldIdentifier(value, memberName); - _validationMessageStore.Add(fieldIdentifier, validationResult.ErrorMessage); - } - } - } - - private void ValidateObject(object value, HashSet<object> visited, List<ValidationResult> validationResults) - { - var validationContext = new ValidationContext(value); - validationContext.Items.Add(ValidationContextValidatorKey, this); - validationContext.Items.Add(ValidatedObjectsKey, visited); - Validator.TryValidateObject(value, validationContext, validationResults, validateAllProperties: true); - } - - internal static bool TryValidateRecursive(object value, ValidationContext validationContext) - { - if (validationContext.Items.TryGetValue(ValidationContextValidatorKey, out var result) && result is ObjectGraphDataAnnotationsValidator validator) - { - var visited = (HashSet<object>)validationContext.Items[ValidatedObjectsKey]; - validator.ValidateObject(value, visited); - - return true; - } - - return false; - } - - private static void ValidateField(EditContext editContext, ValidationMessageStore messages, in FieldIdentifier fieldIdentifier) - { - // DataAnnotations only validates public properties, so that's all we'll look for - var propertyInfo = fieldIdentifier.Model.GetType().GetProperty(fieldIdentifier.FieldName); - if (propertyInfo != null) - { - var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model); - var validationContext = new ValidationContext(fieldIdentifier.Model) - { - MemberName = propertyInfo.Name - }; - var results = new List<ValidationResult>(); - - Validator.TryValidateProperty(propertyValue, validationContext, results); - messages.Clear(fieldIdentifier); - messages.Add(fieldIdentifier, results.Select(result => result.ErrorMessage)); - - // We have to notify even if there were no messages before and are still no messages now, - // because the "state" that changed might be the completion of some async validation task - editContext.NotifyValidationStateChanged(); - } - } - } -} diff --git a/src/Components/Blazor/Validation/src/ValidateComplexTypeAttribute.cs b/src/Components/Blazor/Validation/src/ValidateComplexTypeAttribute.cs deleted file mode 100644 index 4769d84767ad07a1df0f82c7029ee1dd812439f9..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Validation/src/ValidateComplexTypeAttribute.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Components.Forms; - -namespace System.ComponentModel.DataAnnotations -{ - /// <summary> - /// A <see cref="ValidationAttribute"/> that indicates that the property is a complex or collection type that further needs to be validated. - /// <para> - /// By default <see cref="Validator"/> does not recurse in to complex property types during validation. - /// When used in conjunction with <see cref="ObjectGraphDataAnnotationsValidator"/>, this property allows the validation system to validate - /// complex or collection type properties. - /// </para> - /// </summary> - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] - public sealed class ValidateComplexTypeAttribute : ValidationAttribute - { - /// <inheritdoc /> - protected override ValidationResult IsValid(object value, ValidationContext validationContext) - { - if (!ObjectGraphDataAnnotationsValidator.TryValidateRecursive(value, validationContext)) - { - throw new InvalidOperationException($"{nameof(ValidateComplexTypeAttribute)} can only used with {nameof(ObjectGraphDataAnnotationsValidator)}."); - } - - return ValidationResult.Success; - } - } -} diff --git a/src/Components/Blazor/Validation/test/ObjectGraphDataAnnotationsValidatorTest.cs b/src/Components/Blazor/Validation/test/ObjectGraphDataAnnotationsValidatorTest.cs deleted file mode 100644 index 6703eb35d540feff640bf3f104879e5762913325..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/Validation/test/ObjectGraphDataAnnotationsValidatorTest.cs +++ /dev/null @@ -1,540 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using Microsoft.AspNetCore.Components.Forms; -using Xunit; - -namespace Microsoft.AspNetCore.Components -{ - public class ObjectGraphDataAnnotationsValidatorTest - { - public class SimpleModel - { - [Required] - public string Name { get; set; } - - [Range(1, 16)] - public int Age { get; set; } - } - - [Fact] - public void ValidateObject_SimpleObject() - { - var model = new SimpleModel - { - Age = 23, - }; - - var editContext = Validate(model); - var messages = editContext.GetValidationMessages(() => model.Name); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => model.Age); - Assert.Single(messages); - - Assert.Equal(2, editContext.GetValidationMessages().Count()); - } - - [Fact] - public void ValidateObject_SimpleObject_AllValid() - { - var model = new SimpleModel { Name = "Test", Age = 5 }; - - var editContext = Validate(model); - var messages = editContext.GetValidationMessages(() => model.Name); - Assert.Empty(messages); - - messages = editContext.GetValidationMessages(() => model.Age); - Assert.Empty(messages); - - Assert.Empty(editContext.GetValidationMessages()); - } - - public class ModelWithComplexProperty - { - [Required] - public string Property1 { get; set; } - - [ValidateComplexType] - public SimpleModel SimpleModel { get; set; } - } - - [Fact] - public void ValidateObject_NullComplexProperty() - { - var model = new ModelWithComplexProperty(); - - var editContext = Validate(model); - var messages = editContext.GetValidationMessages(() => model.Property1); - Assert.Single(messages); - - Assert.Single(editContext.GetValidationMessages()); - } - - [Fact] - public void ValidateObject_ModelWithComplexProperties() - { - var model = new ModelWithComplexProperty { SimpleModel = new SimpleModel() }; - - var editContext = Validate(model); - var messages = editContext.GetValidationMessages(() => model.Property1); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => model.SimpleModel); - Assert.Empty(messages); - - messages = editContext.GetValidationMessages(() => model.SimpleModel.Age); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => model.SimpleModel.Name); - Assert.Single(messages); - - Assert.Equal(3, editContext.GetValidationMessages().Count()); - } - - [Fact] - public void ValidateObject_ModelWithComplexProperties_SomeValid() - { - var model = new ModelWithComplexProperty - { - Property1 = "Value", - SimpleModel = new SimpleModel { Name = "Some Value" }, - }; - - var editContext = Validate(model); - var messages = editContext.GetValidationMessages(() => model.Property1); - Assert.Empty(messages); - - messages = editContext.GetValidationMessages(() => model.SimpleModel); - Assert.Empty(messages); - - messages = editContext.GetValidationMessages(() => model.SimpleModel.Age); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => model.SimpleModel.Name); - Assert.Empty(messages); - - Assert.Single(editContext.GetValidationMessages()); - } - - public class TestValidatableObject : IValidatableObject - { - [Required] - public string Name { get; set; } - - public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) - { - yield return new ValidationResult("Custom validation error"); - } - } - - public class ModelWithValidatableComplexProperty - { - [Required] - public string Property1 { get; set; } - - [ValidateComplexType] - public TestValidatableObject Property2 { get; set; } = new TestValidatableObject(); - } - - [Fact] - public void ValidateObject_ValidatableComplexProperty() - { - var model = new ModelWithValidatableComplexProperty(); - - var editContext = Validate(model); - var messages = editContext.GetValidationMessages(() => model.Property1); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => model.Property2); - Assert.Empty(messages); - - messages = editContext.GetValidationMessages(() => model.Property2.Name); - Assert.Single(messages); - - Assert.Equal(2, editContext.GetValidationMessages().Count()); - } - - [Fact] - public void ValidateObject_ValidatableComplexProperty_ValidatesIValidatableProperty() - { - var model = new ModelWithValidatableComplexProperty - { - Property2 = new TestValidatableObject { Name = "test" }, - }; - - var editContext = Validate(model); - var messages = editContext.GetValidationMessages(() => model.Property1); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(new FieldIdentifier(model.Property2, string.Empty)); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => model.Property2.Name); - Assert.Empty(messages); - - Assert.Equal(2, editContext.GetValidationMessages().Count()); - } - - [Fact] - public void ValidateObject_ModelIsIValidatable_PropertyHasError() - { - var model = new TestValidatableObject(); - - var editContext = Validate(model); - var messages = editContext.GetValidationMessages(new FieldIdentifier(model, string.Empty)); - Assert.Empty(messages); - - messages = editContext.GetValidationMessages(() => model.Name); - Assert.Single(messages); - - Assert.Single(editContext.GetValidationMessages()); - } - - [Fact] - public void ValidateObject_ModelIsIValidatable_ModelHasError() - { - var model = new TestValidatableObject { Name = "test" }; - - var editContext = Validate(model); - var messages = editContext.GetValidationMessages(new FieldIdentifier(model, string.Empty)); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => model.Name); - Assert.Empty(messages); - - Assert.Single(editContext.GetValidationMessages()); - } - - [Fact] - public void ValidateObject_CollectionModel() - { - var model = new List<SimpleModel> - { - new SimpleModel(), - new SimpleModel { Name = "test", }, - }; - - var editContext = Validate(model); - - var item = model[0]; - var messages = editContext.GetValidationMessages(new FieldIdentifier(model, "0")); - Assert.Empty(messages); - - messages = editContext.GetValidationMessages(() => item.Name); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => item.Age); - Assert.Single(messages); - - item = model[1]; - messages = editContext.GetValidationMessages(new FieldIdentifier(model, "1")); - Assert.Empty(messages); - - messages = editContext.GetValidationMessages(() => item.Name); - Assert.Empty(messages); - - messages = editContext.GetValidationMessages(() => item.Age); - Assert.Single(messages); - - Assert.Equal(3, editContext.GetValidationMessages().Count()); - } - - [Fact] - public void ValidateObject_CollectionValidatableModel() - { - var model = new List<TestValidatableObject> - { - new TestValidatableObject(), - new TestValidatableObject { Name = "test", }, - }; - - var editContext = Validate(model); - - var item = model[0]; - var messages = editContext.GetValidationMessages(() => item.Name); - Assert.Single(messages); - - item = model[1]; - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => item.Name); - Assert.Empty(messages); - - Assert.Equal(2, editContext.GetValidationMessages().Count()); - } - - private class Level1Validation - { - [ValidateComplexType] - public Level2Validation Level2 { get; set; } - } - - public class Level2Validation - { - [ValidateComplexType] - public SimpleModel Level3 { get; set; } - } - - [Fact] - public void ValidateObject_ManyLevels() - { - var model = new Level1Validation - { - Level2 = new Level2Validation - { - Level3 = new SimpleModel - { - Age = 47, - } - } - }; - - var editContext = Validate(model); - var level3 = model.Level2.Level3; - - var messages = editContext.GetValidationMessages(() => level3.Name); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => level3.Age); - Assert.Single(messages); - - Assert.Equal(2, editContext.GetValidationMessages().Count()); - } - - private class Person - { - [Required] - public string Name { get; set; } - - [ValidateComplexType] - public Person Related { get; set; } - } - - [Fact] - public void ValidateObject_RecursiveRelation() - { - var model = new Person { Related = new Person() }; - model.Related.Related = model; - - var editContext = Validate(model); - - var messages = editContext.GetValidationMessages(() => model.Name); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => model.Related.Name); - Assert.Single(messages); - - Assert.Equal(2, editContext.GetValidationMessages().Count()); - } - - [Fact] - public void ValidateObject_RecursiveRelation_OverManySteps() - { - var person1 = new Person(); - var person2 = new Person { Name = "Valid name" }; - var person3 = new Person(); - var person4 = new Person(); - - person1.Related = person2; - person2.Related = person3; - person3.Related = person4; - person4.Related = person1; - - var editContext = Validate(person1); - - var messages = editContext.GetValidationMessages(() => person1.Name); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => person2.Name); - Assert.Empty(messages); - - messages = editContext.GetValidationMessages(() => person3.Name); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => person4.Name); - Assert.Single(messages); - - Assert.Equal(3, editContext.GetValidationMessages().Count()); - } - - private class Node - { - [Required] - public string Id { get; set; } - - [ValidateComplexType] - public List<Node> Related { get; set; } = new List<Node>(); - } - - [Fact] - public void ValidateObject_RecursiveRelation_ViaCollection() - { - var node1 = new Node(); - var node2 = new Node { Id = "Valid Id" }; - var node3 = new Node(); - node1.Related.Add(node2); - node2.Related.Add(node3); - node3.Related.Add(node1); - - var editContext = Validate(node1); - - var messages = editContext.GetValidationMessages(() => node1.Id); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => node2.Id); - Assert.Empty(messages); - - messages = editContext.GetValidationMessages(() => node3.Id); - Assert.Single(messages); - - Assert.Equal(2, editContext.GetValidationMessages().Count()); - } - - [Fact] - public void ValidateObject_RecursiveRelation_InCollection() - { - var person1 = new Person(); - var person2 = new Person { Name = "Valid name" }; - var person3 = new Person(); - var person4 = new Person(); - - person1.Related = person2; - person2.Related = person3; - person3.Related = person4; - person4.Related = person1; - - var editContext = Validate(person1); - - var messages = editContext.GetValidationMessages(() => person1.Name); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => person2.Name); - Assert.Empty(messages); - - messages = editContext.GetValidationMessages(() => person3.Name); - Assert.Single(messages); - - messages = editContext.GetValidationMessages(() => person4.Name); - Assert.Single(messages); - - Assert.Equal(3, editContext.GetValidationMessages().Count()); - } - - [Fact] - public void ValidateField_PropertyValid() - { - var model = new SimpleModel { Age = 1 }; - var fieldIdentifier = FieldIdentifier.Create(() => model.Age); - - var editContext = ValidateField(model, fieldIdentifier); - var messages = editContext.GetValidationMessages(fieldIdentifier); - Assert.Empty(messages); - - Assert.Empty(editContext.GetValidationMessages()); - } - - [Fact] - public void ValidateField_PropertyInvalid() - { - var model = new SimpleModel { Age = 42 }; - var fieldIdentifier = FieldIdentifier.Create(() => model.Age); - - var editContext = ValidateField(model, fieldIdentifier); - var messages = editContext.GetValidationMessages(fieldIdentifier); - Assert.Single(messages); - - Assert.Single(editContext.GetValidationMessages()); - } - - [Fact] - public void ValidateField_AfterSubmitValidation() - { - var model = new SimpleModel { Age = 42 }; - var fieldIdentifier = FieldIdentifier.Create(() => model.Age); - - var editContext = Validate(model); - var messages = editContext.GetValidationMessages(fieldIdentifier); - Assert.Single(messages); - - Assert.Equal(2, editContext.GetValidationMessages().Count()); - - model.Age = 4; - - editContext.NotifyFieldChanged(fieldIdentifier); - messages = editContext.GetValidationMessages(fieldIdentifier); - Assert.Empty(messages); - - Assert.Single(editContext.GetValidationMessages()); - } - - [Fact] - public void ValidateField_ModelWithComplexProperty() - { - var model = new ModelWithComplexProperty - { - SimpleModel = new SimpleModel { Age = 1 }, - }; - var fieldIdentifier = FieldIdentifier.Create(() => model.SimpleModel.Name); - - var editContext = ValidateField(model, fieldIdentifier); - var messages = editContext.GetValidationMessages(fieldIdentifier); - Assert.Single(messages); - - Assert.Single(editContext.GetValidationMessages()); - } - - [Fact] - public void ValidateField_ModelWithComplexProperty_AfterSubmitValidation() - { - var model = new ModelWithComplexProperty - { - Property1 = "test", - SimpleModel = new SimpleModel { Age = 29, Name = "Test" }, - }; - var fieldIdentifier = FieldIdentifier.Create(() => model.SimpleModel.Age); - - var editContext = Validate(model); - var messages = editContext.GetValidationMessages(fieldIdentifier); - Assert.Single(messages); - - model.SimpleModel.Age = 9; - editContext.NotifyFieldChanged(fieldIdentifier); - - messages = editContext.GetValidationMessages(fieldIdentifier); - Assert.Empty(messages); - Assert.Empty(editContext.GetValidationMessages()); - } - - private static EditContext Validate(object model) - { - var editContext = new EditContext(model); - var validator = new TestObjectGraphDataAnnotationsValidator { EditContext = editContext, }; - validator.OnInitialized(); - - editContext.Validate(); - - return editContext; - } - - private static EditContext ValidateField(object model, in FieldIdentifier field) - { - var editContext = new EditContext(model); - var validator = new TestObjectGraphDataAnnotationsValidator { EditContext = editContext, }; - validator.OnInitialized(); - - editContext.NotifyFieldChanged(field); - - return editContext; - } - - private class TestObjectGraphDataAnnotationsValidator : ObjectGraphDataAnnotationsValidator - { - public new void OnInitialized() => base.OnInitialized(); - } - } -} diff --git a/src/Components/Blazor/testassets/HostedInAspNet.Client/HostedInAspNet.Client.csproj b/src/Components/Blazor/testassets/HostedInAspNet.Client/HostedInAspNet.Client.csproj deleted file mode 100644 index e27de695c1b33fc9a9072a112dae9f848ac76674..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/testassets/HostedInAspNet.Client/HostedInAspNet.Client.csproj +++ /dev/null @@ -1,14 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk.Web"> - - <PropertyGroup> - <TargetFramework>netstandard2.1</TargetFramework> - <OutputType>Exe</OutputType> - <ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally> - <RazorLangVersion>3.0</RazorLangVersion> - </PropertyGroup> - - <ItemGroup> - <Reference Include="Microsoft.AspNetCore.Blazor" /> - </ItemGroup> - -</Project> diff --git a/src/Components/Blazor/testassets/HostedInAspNet.Client/wwwroot/index.html b/src/Components/Blazor/testassets/HostedInAspNet.Client/wwwroot/index.html deleted file mode 100644 index a47b1ad84ed2a9b1982b79d12c527cf12f08fb20..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/testassets/HostedInAspNet.Client/wwwroot/index.html +++ /dev/null @@ -1,12 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <meta charset="utf-8" /> - <title>Sample Blazor app</title> -</head> -<body> - <app>Loading...</app> - <script src="customJsFileForTests.js"></script> - <script src="_framework/blazor.webassembly.js"></script> -</body> -</html> diff --git a/src/Components/Blazor/testassets/StandaloneApp/StandaloneApp.csproj b/src/Components/Blazor/testassets/StandaloneApp/StandaloneApp.csproj deleted file mode 100644 index 32156c56b85475920dbc7be86f1132726412f90c..0000000000000000000000000000000000000000 --- a/src/Components/Blazor/testassets/StandaloneApp/StandaloneApp.csproj +++ /dev/null @@ -1,13 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk.Web"> - - <PropertyGroup> - <TargetFramework>netstandard2.1</TargetFramework> - <ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally> - <RazorLangVersion>3.0</RazorLangVersion> - </PropertyGroup> - - <ItemGroup> - <Reference Include="Microsoft.AspNetCore.Blazor" /> - <Reference Include="Microsoft.AspNetCore.Blazor.HttpClient" /> - </ItemGroup> -</Project> diff --git a/src/Components/Components.sln b/src/Components/Components.sln index c88695cf66e314ee6e03637f3c6b55d642a8004a..99c12e091dd9ba90b2bf898aeb27c8d5b5c45b0c 100644 --- a/src/Components/Components.sln +++ b/src/Components/Components.sln @@ -9,19 +9,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Compon EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Analyzers.Tests", "Analyzers\test\Microsoft.AspNetCore.Components.Analyzers.Tests.csproj", "{F000C49D-3857-42A4-918D-DA4C08691FE2}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Blazor", "Blazor", "{7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAssembly", "WebAssembly", "{7260DED9-22A9-4E9D-92F4-5E8A4404DEAF}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor", "Blazor\Blazor\src\Microsoft.AspNetCore.Blazor.csproj", "{641922CD-E6F5-41E7-A085-EE07C2A7328D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly", "WebAssembly\WebAssembly\src\Microsoft.AspNetCore.Components.WebAssembly.csproj", "{641922CD-E6F5-41E7-A085-EE07C2A7328D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Tests", "Blazor\Blazor\test\Microsoft.AspNetCore.Blazor.Tests.csproj", "{958AD6D2-174B-4B5B-BEFC-FA64B5159334}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Tests", "WebAssembly\WebAssembly\test\Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj", "{958AD6D2-174B-4B5B-BEFC-FA64B5159334}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Build", "Blazor\Build\src\Microsoft.AspNetCore.Blazor.Build.csproj", "{E8AD67A4-77D3-4B85-AE19-4711388B62B1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Build", "WebAssembly\Build\src\Microsoft.AspNetCore.Components.WebAssembly.Build.csproj", "{E8AD67A4-77D3-4B85-AE19-4711388B62B1}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Build.Tests", "Blazor\Build\test\Microsoft.AspNetCore.Blazor.Build.Tests.csproj", "{E38FDBB0-08C1-444E-A449-69C8A59D721B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Build.Tests", "WebAssembly\Build\test\Microsoft.AspNetCore.Components.WebAssembly.Build.Tests.csproj", "{E38FDBB0-08C1-444E-A449-69C8A59D721B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DevServer", "Blazor\DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj", "{A6C8050D-7C18-4585-ADCF-833AC1765847}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.DevServer", "WebAssembly\DevServer\src\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj", "{A6C8050D-7C18-4585-ADCF-833AC1765847}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Server", "Blazor\Server\src\Microsoft.AspNetCore.Blazor.Server.csproj", "{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Server", "WebAssembly\Server\src\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj", "{A4859630-F9F7-4F5C-9FF3-6C013D7C58FA}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{A7ABAC29-F73F-456D-AE54-46842CFC2E10}" EndProject @@ -206,10 +206,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Signal EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Connections.Client", "..\SignalR\clients\csharp\Http.Connections.Client\src\Microsoft.AspNetCore.Http.Connections.Client.csproj", "{F88118E1-6F4A-4F89-B047-5FFD2889B9F0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.HttpClient", "Blazor\Http\src\Microsoft.AspNetCore.Blazor.HttpClient.csproj", "{74D21785-2FAB-4266-B7C4-E311EC8EE0DF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.HttpClient.Tests", "Blazor\Http\test\Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj", "{E4C01A3F-D3C1-4639-A6A9-930E918843DD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Web.Tests", "Web\test\Microsoft.AspNetCore.Components.Web.Tests.csproj", "{DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authorization", "Authorization", "{08791FEE-761D-40EF-B701-1D31FD1E6E53}" @@ -236,23 +232,41 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor", "Ignitor\src\Igni EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ignitor.Test", "Ignitor\test\Ignitor.Test.csproj", "{F31E8118-014E-4CCE-8A48-5282F7B9BB3E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation", "Blazor\Validation\src\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj", "{B70F90C7-2696-4050-B24E-BF0308F4E059}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsApp.Server", "test\testassets\ComponentsApp.Server\ComponentsApp.Server.csproj", "{F2E27E1C-2E47-42C1-9AC7-36265A381717}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests", "Blazor\Validation\test\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj", "{A5617A9D-C71E-44DE-936C-27611EB40A02}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{CCC82E97-7B58-43E2-BBBD-23D82F926367}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mono.WebAssembly.Interop", "Mono.WebAssembly.Interop", "{21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wasm.Performance", "Wasm.Performance", "{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.WebAssembly.Interop", "Blazor\Mono.WebAssembly.Interop\src\Mono.WebAssembly.Interop.csproj", "{D141CFEE-D10A-406B-8963-F86FA13732E3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasm.Performance.Driver", "benchmarkapps\Wasm.Performance\Driver\Wasm.Performance.Driver.csproj", "{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsApp.Server", "test\testassets\ComponentsApp.Server\ComponentsApp.Server.csproj", "{F2E27E1C-2E47-42C1-9AC7-36265A381717}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasm.Performance.TestApp", "benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj", "{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarkapps", "benchmarkapps", "{CCC82E97-7B58-43E2-BBBD-23D82F926367}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAssembly", "WebAssembly", "{346EC9B8-BF36-4A5E-A1A3-77879931713A}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Wasm.Performance", "Wasm.Performance", "{F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{1FA95650-E56E-470A-82A3-BC6572E4D6CD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Server.Tests", "WebAssembly\Server\test\Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj", "{3D0ED658-9DAC-4066-A587-795321FA1C98}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{42E3C95D-A41E-4E14-96FD-AAE8F340FD7E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Authentication.WebAssembly.Msal", "WebAssembly\Authentication.Msal\src\Microsoft.Authentication.WebAssembly.Msal.csproj", "{4B4E4247-7BBF-444E-9737-407D34821D70}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression", "WebAssembly\Compression\src\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj", "{1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.DebugProxy", "WebAssembly\DebugProxy\src\Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj", "{B118AE2F-8D1D-413F-BC5D-060DF7CB707D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DevServer", "DevServer", "{C6B58D53-04E2-4D65-B445-B510A3CB7569}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.JSInterop.WebAssembly", "WebAssembly\JSInterop\src\Microsoft.JSInterop.WebAssembly.csproj", "{8FDD9F2E-B940-4A5F-83FD-5486D0853D76}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Authentication", "WebAssembly\WebAssembly.Authentication\src\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj", "{5CD61479-5181-4A5B-B90F-9F34316248B3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests", "WebAssembly\WebAssembly.Authentication\test\Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj", "{6B0D6C08-FC30-4822-9464-4D24FF4CDC17}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.Driver", "benchmarkapps\Wasm.Performance\Driver\Wasm.Performance.Driver.csproj", "{CA9948CA-B3FA-4C2E-A726-5E47BAD19457}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication", "Authentication", "{81250121-9B43-40B1-BF11-CE4458F2676C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wasm.Performance.TestApp", "benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj", "{97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.HttpHandler", "WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj", "{031AD67E-DDDE-4A20-874A-8D0A791C6F4C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1344,30 +1358,6 @@ Global {F88118E1-6F4A-4F89-B047-5FFD2889B9F0}.Release|x64.Build.0 = Release|Any CPU {F88118E1-6F4A-4F89-B047-5FFD2889B9F0}.Release|x86.ActiveCfg = Release|Any CPU {F88118E1-6F4A-4F89-B047-5FFD2889B9F0}.Release|x86.Build.0 = Release|Any CPU - {74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x64.ActiveCfg = Debug|Any CPU - {74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x64.Build.0 = Debug|Any CPU - {74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x86.ActiveCfg = Debug|Any CPU - {74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Debug|x86.Build.0 = Debug|Any CPU - {74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|Any CPU.Build.0 = Release|Any CPU - {74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x64.ActiveCfg = Release|Any CPU - {74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x64.Build.0 = Release|Any CPU - {74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x86.ActiveCfg = Release|Any CPU - {74D21785-2FAB-4266-B7C4-E311EC8EE0DF}.Release|x86.Build.0 = Release|Any CPU - {E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x64.ActiveCfg = Debug|Any CPU - {E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x64.Build.0 = Debug|Any CPU - {E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x86.ActiveCfg = Debug|Any CPU - {E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Debug|x86.Build.0 = Debug|Any CPU - {E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|Any CPU.Build.0 = Release|Any CPU - {E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x64.ActiveCfg = Release|Any CPU - {E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x64.Build.0 = Release|Any CPU - {E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x86.ActiveCfg = Release|Any CPU - {E4C01A3F-D3C1-4639-A6A9-930E918843DD}.Release|x86.Build.0 = Release|Any CPU {DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}.Debug|Any CPU.Build.0 = Debug|Any CPU {DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1476,42 +1466,6 @@ Global {F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x64.Build.0 = Release|Any CPU {F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x86.ActiveCfg = Release|Any CPU {F31E8118-014E-4CCE-8A48-5282F7B9BB3E}.Release|x86.Build.0 = Release|Any CPU - {B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x64.ActiveCfg = Debug|Any CPU - {B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x64.Build.0 = Debug|Any CPU - {B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x86.ActiveCfg = Debug|Any CPU - {B70F90C7-2696-4050-B24E-BF0308F4E059}.Debug|x86.Build.0 = Debug|Any CPU - {B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|Any CPU.Build.0 = Release|Any CPU - {B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x64.ActiveCfg = Release|Any CPU - {B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x64.Build.0 = Release|Any CPU - {B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x86.ActiveCfg = Release|Any CPU - {B70F90C7-2696-4050-B24E-BF0308F4E059}.Release|x86.Build.0 = Release|Any CPU - {A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x64.ActiveCfg = Debug|Any CPU - {A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x64.Build.0 = Debug|Any CPU - {A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x86.ActiveCfg = Debug|Any CPU - {A5617A9D-C71E-44DE-936C-27611EB40A02}.Debug|x86.Build.0 = Debug|Any CPU - {A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|Any CPU.Build.0 = Release|Any CPU - {A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x64.ActiveCfg = Release|Any CPU - {A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x64.Build.0 = Release|Any CPU - {A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x86.ActiveCfg = Release|Any CPU - {A5617A9D-C71E-44DE-936C-27611EB40A02}.Release|x86.Build.0 = Release|Any CPU - {D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x64.ActiveCfg = Debug|Any CPU - {D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x64.Build.0 = Debug|Any CPU - {D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x86.ActiveCfg = Debug|Any CPU - {D141CFEE-D10A-406B-8963-F86FA13732E3}.Debug|x86.Build.0 = Debug|Any CPU - {D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|Any CPU.Build.0 = Release|Any CPU - {D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x64.ActiveCfg = Release|Any CPU - {D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x64.Build.0 = Release|Any CPU - {D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x86.ActiveCfg = Release|Any CPU - {D141CFEE-D10A-406B-8963-F86FA13732E3}.Release|x86.Build.0 = Release|Any CPU {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|Any CPU.Build.0 = Debug|Any CPU {F2E27E1C-2E47-42C1-9AC7-36265A381717}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1548,6 +1502,102 @@ Global {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x64.Build.0 = Release|Any CPU {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.ActiveCfg = Release|Any CPU {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB}.Release|x86.Build.0 = Release|Any CPU + {3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x64.ActiveCfg = Debug|Any CPU + {3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x64.Build.0 = Debug|Any CPU + {3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x86.ActiveCfg = Debug|Any CPU + {3D0ED658-9DAC-4066-A587-795321FA1C98}.Debug|x86.Build.0 = Debug|Any CPU + {3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|Any CPU.Build.0 = Release|Any CPU + {3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x64.ActiveCfg = Release|Any CPU + {3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x64.Build.0 = Release|Any CPU + {3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x86.ActiveCfg = Release|Any CPU + {3D0ED658-9DAC-4066-A587-795321FA1C98}.Release|x86.Build.0 = Release|Any CPU + {4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x64.ActiveCfg = Debug|Any CPU + {4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x64.Build.0 = Debug|Any CPU + {4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x86.ActiveCfg = Debug|Any CPU + {4B4E4247-7BBF-444E-9737-407D34821D70}.Debug|x86.Build.0 = Debug|Any CPU + {4B4E4247-7BBF-444E-9737-407D34821D70}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B4E4247-7BBF-444E-9737-407D34821D70}.Release|Any CPU.Build.0 = Release|Any CPU + {4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x64.ActiveCfg = Release|Any CPU + {4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x64.Build.0 = Release|Any CPU + {4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x86.ActiveCfg = Release|Any CPU + {4B4E4247-7BBF-444E-9737-407D34821D70}.Release|x86.Build.0 = Release|Any CPU + {1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x64.ActiveCfg = Debug|Any CPU + {1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x64.Build.0 = Debug|Any CPU + {1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x86.ActiveCfg = Debug|Any CPU + {1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Debug|x86.Build.0 = Debug|Any CPU + {1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|Any CPU.Build.0 = Release|Any CPU + {1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x64.ActiveCfg = Release|Any CPU + {1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x64.Build.0 = Release|Any CPU + {1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x86.ActiveCfg = Release|Any CPU + {1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE}.Release|x86.Build.0 = Release|Any CPU + {B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x64.ActiveCfg = Debug|Any CPU + {B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x64.Build.0 = Debug|Any CPU + {B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x86.ActiveCfg = Debug|Any CPU + {B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Debug|x86.Build.0 = Debug|Any CPU + {B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|Any CPU.Build.0 = Release|Any CPU + {B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x64.ActiveCfg = Release|Any CPU + {B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x64.Build.0 = Release|Any CPU + {B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x86.ActiveCfg = Release|Any CPU + {B118AE2F-8D1D-413F-BC5D-060DF7CB707D}.Release|x86.Build.0 = Release|Any CPU + {8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x64.ActiveCfg = Debug|Any CPU + {8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x64.Build.0 = Debug|Any CPU + {8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x86.ActiveCfg = Debug|Any CPU + {8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Debug|x86.Build.0 = Debug|Any CPU + {8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|Any CPU.Build.0 = Release|Any CPU + {8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x64.ActiveCfg = Release|Any CPU + {8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x64.Build.0 = Release|Any CPU + {8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x86.ActiveCfg = Release|Any CPU + {8FDD9F2E-B940-4A5F-83FD-5486D0853D76}.Release|x86.Build.0 = Release|Any CPU + {5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x64.ActiveCfg = Debug|Any CPU + {5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x64.Build.0 = Debug|Any CPU + {5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x86.ActiveCfg = Debug|Any CPU + {5CD61479-5181-4A5B-B90F-9F34316248B3}.Debug|x86.Build.0 = Debug|Any CPU + {5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|Any CPU.Build.0 = Release|Any CPU + {5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x64.ActiveCfg = Release|Any CPU + {5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x64.Build.0 = Release|Any CPU + {5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x86.ActiveCfg = Release|Any CPU + {5CD61479-5181-4A5B-B90F-9F34316248B3}.Release|x86.Build.0 = Release|Any CPU + {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x64.ActiveCfg = Debug|Any CPU + {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x64.Build.0 = Debug|Any CPU + {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x86.ActiveCfg = Debug|Any CPU + {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Debug|x86.Build.0 = Debug|Any CPU + {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|Any CPU.Build.0 = Release|Any CPU + {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x64.ActiveCfg = Release|Any CPU + {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x64.Build.0 = Release|Any CPU + {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x86.ActiveCfg = Release|Any CPU + {6B0D6C08-FC30-4822-9464-4D24FF4CDC17}.Release|x86.Build.0 = Release|Any CPU + {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x64.ActiveCfg = Debug|Any CPU + {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x64.Build.0 = Debug|Any CPU + {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x86.ActiveCfg = Debug|Any CPU + {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Debug|x86.Build.0 = Debug|Any CPU + {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|Any CPU.Build.0 = Release|Any CPU + {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x64.ActiveCfg = Release|Any CPU + {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x64.Build.0 = Release|Any CPU + {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x86.ActiveCfg = Release|Any CPU + {031AD67E-DDDE-4A20-874A-8D0A791C6F4C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1555,12 +1605,12 @@ Global GlobalSection(NestedProjects) = preSolution {ECE91401-329E-4615-8684-8E910D2741C4} = {E059A46B-56E3-41E2-83F4-B5D180056F3B} {F000C49D-3857-42A4-918D-DA4C08691FE2} = {E059A46B-56E3-41E2-83F4-B5D180056F3B} - {641922CD-E6F5-41E7-A085-EE07C2A7328D} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} - {958AD6D2-174B-4B5B-BEFC-FA64B5159334} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} - {E8AD67A4-77D3-4B85-AE19-4711388B62B1} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} - {E38FDBB0-08C1-444E-A449-69C8A59D721B} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} - {A6C8050D-7C18-4585-ADCF-833AC1765847} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} - {A4859630-F9F7-4F5C-9FF3-6C013D7C58FA} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} + {641922CD-E6F5-41E7-A085-EE07C2A7328D} = {346EC9B8-BF36-4A5E-A1A3-77879931713A} + {958AD6D2-174B-4B5B-BEFC-FA64B5159334} = {346EC9B8-BF36-4A5E-A1A3-77879931713A} + {E8AD67A4-77D3-4B85-AE19-4711388B62B1} = {1FA95650-E56E-470A-82A3-BC6572E4D6CD} + {E38FDBB0-08C1-444E-A449-69C8A59D721B} = {1FA95650-E56E-470A-82A3-BC6572E4D6CD} + {A6C8050D-7C18-4585-ADCF-833AC1765847} = {C6B58D53-04E2-4D65-B445-B510A3CB7569} + {A4859630-F9F7-4F5C-9FF3-6C013D7C58FA} = {42E3C95D-A41E-4E14-96FD-AAE8F340FD7E} {A7ABAC29-F73F-456D-AE54-46842CFC2E10} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} {FD37F740-A654-4117-BFB6-9112CE4C1D3B} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10} {C1E2C117-BE47-4E29-94B3-753262D97A5C} = {A7ABAC29-F73F-456D-AE54-46842CFC2E10} @@ -1645,8 +1695,6 @@ Global {DA137BD4-F7F1-4D53-855F-5EC40CEA36B0} = {2FC10057-7A0A-4E34-8302-879925BC0102} {0CDAB70B-71DC-43BE-ACB7-AD2EE3541FFB} = {2FC10057-7A0A-4E34-8302-879925BC0102} {F88118E1-6F4A-4F89-B047-5FFD2889B9F0} = {2FC10057-7A0A-4E34-8302-879925BC0102} - {74D21785-2FAB-4266-B7C4-E311EC8EE0DF} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} - {E4C01A3F-D3C1-4639-A6A9-930E918843DD} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} {DE297C91-B3E9-4C6F-B74D-0AF9EFEBF684} = {A27FF193-195B-4474-8E6C-840B2E339373} {956F540A-3CDA-4913-9373-1A4E8A93BDD8} = {08791FEE-761D-40EF-B701-1D31FD1E6E53} {B13CDE69-ED22-4664-AAD7-686ED8CD5E88} = {08791FEE-761D-40EF-B701-1D31FD1E6E53} @@ -1656,14 +1704,23 @@ Global {BBF37AF9-8290-4B70-8BA8-0F6017B3B620} = {46E4300C-5726-4108-B9A2-18BB94EB26ED} {CD0EF85C-4187-4515-A355-E5A0D4485F40} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926} {F31E8118-014E-4CCE-8A48-5282F7B9BB3E} = {BDE2397D-C53A-4783-8B3A-1F54F48A6926} - {B70F90C7-2696-4050-B24E-BF0308F4E059} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} - {A5617A9D-C71E-44DE-936C-27611EB40A02} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} - {21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} - {D141CFEE-D10A-406B-8963-F86FA13732E3} = {21BB9C13-20C1-4F2B-80E4-D7C64AA3BD05} {F2E27E1C-2E47-42C1-9AC7-36265A381717} = {44E0D4F3-4430-4175-B482-0D1AEE4BB699} {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A} = {CCC82E97-7B58-43E2-BBBD-23D82F926367} {CA9948CA-B3FA-4C2E-A726-5E47BAD19457} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A} {97EA0A7D-FE5E-47D1-ADDC-4BFD702F55AB} = {F65EFF0F-ACF3-46BD-9A8F-CDA94AF1885A} + {346EC9B8-BF36-4A5E-A1A3-77879931713A} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} + {1FA95650-E56E-470A-82A3-BC6572E4D6CD} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} + {3D0ED658-9DAC-4066-A587-795321FA1C98} = {42E3C95D-A41E-4E14-96FD-AAE8F340FD7E} + {42E3C95D-A41E-4E14-96FD-AAE8F340FD7E} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} + {4B4E4247-7BBF-444E-9737-407D34821D70} = {81250121-9B43-40B1-BF11-CE4458F2676C} + {1A4C96E8-3FAF-48FB-9F3C-068FAAAB3FEE} = {1FA95650-E56E-470A-82A3-BC6572E4D6CD} + {B118AE2F-8D1D-413F-BC5D-060DF7CB707D} = {C6B58D53-04E2-4D65-B445-B510A3CB7569} + {C6B58D53-04E2-4D65-B445-B510A3CB7569} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} + {8FDD9F2E-B940-4A5F-83FD-5486D0853D76} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} + {5CD61479-5181-4A5B-B90F-9F34316248B3} = {81250121-9B43-40B1-BF11-CE4458F2676C} + {6B0D6C08-FC30-4822-9464-4D24FF4CDC17} = {81250121-9B43-40B1-BF11-CE4458F2676C} + {81250121-9B43-40B1-BF11-CE4458F2676C} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} + {031AD67E-DDDE-4A20-874A-8D0A791C6F4C} = {7260DED9-22A9-4E9D-92F4-5E8A4404DEAF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {CC3C47E1-AD1A-4619-9CD3-E08A0148E5CE} diff --git a/src/Components/ComponentsNoDeps.slnf b/src/Components/ComponentsNoDeps.slnf index 7e09eeea25ce2b1d9244ceae73a642223b192f83..df42481d3cf8a4799451e94580ea1d83030c3c8d 100644 --- a/src/Components/ComponentsNoDeps.slnf +++ b/src/Components/ComponentsNoDeps.slnf @@ -6,22 +6,6 @@ "Analyzers\\test\\Microsoft.AspNetCore.Components.Analyzers.Tests.csproj", "Authorization\\src\\Microsoft.AspNetCore.Components.Authorization.csproj", "Authorization\\test\\Microsoft.AspNetCore.Components.Authorization.Tests.csproj", - "Blazor\\Blazor\\src\\Microsoft.AspNetCore.Blazor.csproj", - "Blazor\\Blazor\\test\\Microsoft.AspNetCore.Blazor.Tests.csproj", - "Blazor\\Build\\src\\Microsoft.AspNetCore.Blazor.Build.csproj", - "Blazor\\Build\\test\\Microsoft.AspNetCore.Blazor.Build.Tests.csproj", - "Blazor\\DevServer\\src\\Microsoft.AspNetCore.Blazor.DevServer.csproj", - "Blazor\\Http\\src\\Microsoft.AspNetCore.Blazor.HttpClient.csproj", - "Blazor\\Http\\test\\Microsoft.AspNetCore.Blazor.HttpClient.Tests.csproj", - "Blazor\\Mono.WebAssembly.Interop\\src\\Mono.WebAssembly.Interop.csproj", - "Blazor\\Server\\src\\Microsoft.AspNetCore.Blazor.Server.csproj", - "Blazor\\Validation\\src\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.csproj", - "Blazor\\Validation\\test\\Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj", - "Blazor\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj", - "Blazor\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj", - "Blazor\\testassets\\MonoSanityClient\\MonoSanityClient.csproj", - "Blazor\\testassets\\MonoSanity\\MonoSanity.csproj", - "Blazor\\testassets\\StandaloneApp\\StandaloneApp.csproj", "Components\\perf\\Microsoft.AspNetCore.Components.Performance.csproj", "Components\\src\\Microsoft.AspNetCore.Components.csproj", "Components\\test\\Microsoft.AspNetCore.Components.Tests.csproj", @@ -32,12 +16,27 @@ "Samples\\BlazorServerApp\\BlazorServerApp.csproj", "Server\\src\\Microsoft.AspNetCore.Components.Server.csproj", "Server\\test\\Microsoft.AspNetCore.Components.Server.Tests.csproj", + "WebAssembly\\Authentication.Msal\\src\\Microsoft.Authentication.WebAssembly.Msal.csproj", + "WebAssembly\\Build\\src\\Microsoft.AspNetCore.Components.WebAssembly.Build.csproj", + "WebAssembly\\Build\\test\\Microsoft.AspNetCore.Components.WebAssembly.Build.Tests.csproj", + "WebAssembly\\Compression\\src\\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj", + "WebAssembly\\DebugProxy\\src\\Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj", + "WebAssembly\\DevServer\\src\\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj", + "WebAssembly\\JSInterop\\src\\Microsoft.JSInterop.WebAssembly.csproj", + "WebAssembly\\Server\\src\\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj", + "WebAssembly\\Server\\test\\Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj", + "WebAssembly\\WebAssembly.Authentication\\src\\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj", + "WebAssembly\\WebAssembly.Authentication\\test\\Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj", + "WebAssembly\\WebAssemblyHttpHandler\\src\\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj", + "WebAssembly\\WebAssembly\\src\\Microsoft.AspNetCore.Components.WebAssembly.csproj", + "WebAssembly\\WebAssembly\\test\\Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj", "Web\\src\\Microsoft.AspNetCore.Components.Web.csproj", "Web\\test\\Microsoft.AspNetCore.Components.Web.Tests.csproj", "benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj", "benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj", "test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj", "test\\testassets\\BasicTestApp\\BasicTestApp.csproj", + "test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj", "test\\testassets\\TestContentPackage\\TestContentPackage.csproj", "test\\testassets\\TestServer\\Components.TestServer.csproj" ] diff --git a/src/Components/Directory.Build.props b/src/Components/Directory.Build.props index fb709cba972d4373a5b61f0b6581992f55a3a5d7..9fe61c3404de94ddc56a1bc37f9e2539b0063a56 100644 --- a/src/Components/Directory.Build.props +++ b/src/Components/Directory.Build.props @@ -10,11 +10,11 @@ </PropertyGroup> <PropertyGroup> - <PackageTags>aspnetcore;components</PackageTags> + <EnableTypeScriptNuGetTarget>true</EnableTypeScriptNuGetTarget> + </PropertyGroup> - <!-- This property points to the latest released Microsoft.AspNetCore.App version it needs to be updated to - target the latest patch before a preview release. --> - <LatestAspNetCoreReferenceVersion>3.1.0</LatestAspNetCoreReferenceVersion> + <PropertyGroup> + <PackageTags>aspnetcore;components</PackageTags> <ComponentsSharedSourceRoot>$(MSBuildThisFileDirectory)Shared\</ComponentsSharedSourceRoot> diff --git a/src/Components/Directory.Build.targets b/src/Components/Directory.Build.targets index d6569c4088f4d196c4b4e3e44f61a2003ac1501c..c72c9c4c35b3c52ed2d86cbcf46a7ee5936279f1 100644 --- a/src/Components/Directory.Build.targets +++ b/src/Components/Directory.Build.targets @@ -3,26 +3,6 @@ <GenerateDocumentationFile Condition="'$(GenerateDocumentationFile)' == ''">true</GenerateDocumentationFile> </PropertyGroup> - <!-- We need to do this because our build config interferes with the FrameworkReference definition. - This is a way to add the framework defition to the projects that need it (like Blazor Server and - Blazor Dev Server) --> - <Target Name="_AddAspNetCoreFrameworkReference" BeforeTargets="ProcessFrameworkReferences" Condition="'$(UseLatestAspNetCoreReference)' == 'true' "> - <ItemGroup> - <FrameworkReference Include="Microsoft.AspNetCore.App" Version="$(LatestAspNetCoreReferenceVersion)" /> - <KnownFrameworkReference Include="Microsoft.AspNetCore.App"> - <TargetFramework>netcoreapp3.1</TargetFramework> - <RuntimeFrameworkName>Microsoft.AspNetCore.App</RuntimeFrameworkName> - <DefaultRuntimeFrameworkVersion>$(LatestAspNetCoreReferenceVersion)</DefaultRuntimeFrameworkVersion> - <LatestRuntimeFrameworkVersion>$(LatestAspNetCoreReferenceVersion)</LatestRuntimeFrameworkVersion> - <TargetingPackName>Microsoft.AspNetCore.App.Ref</TargetingPackName> - <TargetingPackVersion>$(LatestAspNetCoreReferenceVersion)</TargetingPackVersion> - <RuntimePackNamePatterns>Microsoft.AspNetCore.App.Runtime.**RID**</RuntimePackNamePatterns> - <RuntimePackRuntimeIdentifiers>linux-arm;linux-arm64;linux-musl-arm64;linux-musl-x64;linux-x64;osx-x64;rhel.6-x64;tizen.4.0.0-armel;tizen.5.0.0-armel;win-arm;win-arm64;win-x64;win-x86</RuntimePackRuntimeIdentifiers> - <IsTrimmable>true</IsTrimmable> - </KnownFrameworkReference> - </ItemGroup> - </Target> - <ItemGroup> <!-- Add a project dependency without reference output assemblies to enforce build order --> <!-- Applying workaround for https://github.com/microsoft/msbuild/issues/2661 and https://github.com/dotnet/sdk/issues/952 --> @@ -35,7 +15,29 @@ Private="false" /> </ItemGroup> - <Import Project="Blazor\Build\src\ReferenceFromSource.props" Condition="'$(ReferenceBlazorBuildLocally)' == 'true'" /> + <Import Project="WebAssembly\Build\src\ReferenceFromSource.props" Condition="'$(ReferenceBlazorBuildLocally)' == 'true'" /> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" /> + + <ItemGroup Condition="'$(FixupWebAssemblyHttpHandlerReference)' == 'true'"> + <ProjectReference + Include="$(RepoRoot)src\Components\WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj" + CopyLocal="false" /> + </ItemGroup> + + <Target Name="_FixupReferenceToWebAssemblyHttpHandler" + Condition="'$(FixupWebAssemblyHttpHandlerReference)' == 'true'" + AfterTargets="_ResolveBlazorInputs" + BeforeTargets="_ResolveBlazorOutputs"> + <!-- + ProjectReference doesn't really play well with IncludeAssets behavior you get when referencing + the package with IncludeAssets="compile". + --> + <ItemGroup> + <_HttpHandlerAssembly Include="@(_BlazorUserRuntimeAssembly)" + Condition="%(_BlazorUserRuntimeAssembly.ProjectReferenceOriginalItemSpec) == '$(RepoRoot)src\Components\WebAssembly\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj'" /> + + <_BlazorUserRuntimeAssembly Remove="@(_HttpHandlerAssembly)" /> + </ItemGroup> + </Target> </Project> diff --git a/src/Components/Samples/BlazorServerApp/BlazorServerApp.csproj b/src/Components/Samples/BlazorServerApp/BlazorServerApp.csproj index 2a82b7453aa58b0fb6cd048dd2d9f892669f86dd..101fe45c133b8a3b442623b03669cb9094afcf45 100644 --- a/src/Components/Samples/BlazorServerApp/BlazorServerApp.csproj +++ b/src/Components/Samples/BlazorServerApp/BlazorServerApp.csproj @@ -12,7 +12,6 @@ <Reference Include="Microsoft.AspNetCore.HttpsPolicy" /> <Reference Include="Microsoft.AspNetCore.Mvc" /> <Reference Include="Microsoft.Extensions.Hosting" /> - <Reference Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" /> </ItemGroup> </Project> diff --git a/src/Components/Web.JS/dist/Release/blazor.server.js b/src/Components/Web.JS/dist/Release/blazor.server.js index 30d3ae39491b6f3d13a946fce4931aa230d09955..98d9cb6e392cf93ed7dd3ce12e2f63f3f7d111be 100644 Binary files a/src/Components/Web.JS/dist/Release/blazor.server.js and b/src/Components/Web.JS/dist/Release/blazor.server.js differ diff --git a/src/Components/Web.JS/dist/Release/blazor.webassembly.js b/src/Components/Web.JS/dist/Release/blazor.webassembly.js index 16dd578625ff37c883bf7afba118a1adb6e67981..a12eaa04dc39e98313a08660c19ac42ae729cc79 100644 Binary files a/src/Components/Web.JS/dist/Release/blazor.webassembly.js and b/src/Components/Web.JS/dist/Release/blazor.webassembly.js differ diff --git a/src/Components/Web.JS/package.json b/src/Components/Web.JS/package.json index ca36ee98080fcb226fbecfb45e97b5829c9e1271..7e6794d764bb42519ec0f8293553bc3031ccea0d 100644 --- a/src/Components/Web.JS/package.json +++ b/src/Components/Web.JS/package.json @@ -17,19 +17,19 @@ "@aspnet/signalr": "link:../../SignalR/clients/ts/signalr", "@aspnet/signalr-protocol-msgpack": "link:../../SignalR/clients/ts/signalr-protocol-msgpack", "@dotnet/jsinterop": "https://dotnet.myget.org/F/aspnetcore-dev/npm/@dotnet/jsinterop/-/@dotnet/jsinterop-3.0.0-preview9.19415.3.tgz", - "@types/emscripten": "0.0.31", - "@types/jest": "^24.0.6", + "@types/emscripten": "^1.39.3", + "@types/jest": "^24.9.1", "@types/jsdom": "11.0.6", - "@typescript-eslint/eslint-plugin": "^1.5.0", - "@typescript-eslint/parser": "^1.5.0", + "@typescript-eslint/eslint-plugin": "^1.13.0", + "@typescript-eslint/parser": "^1.13.0", "eslint": "^5.16.0", - "jest": "^24.8.0", - "rimraf": "^2.6.2", - "ts-jest": "^24.0.0", + "jest": "^24.9.0", + "rimraf": "^2.7.1", + "ts-jest": "^24.3.0", "ts-loader": "^4.4.1", - "typescript": "^3.5.3", - "webpack": "^4.36.1", - "webpack-cli": "^3.3.6" + "typescript": "^3.8.3", + "webpack": "^4.42.1", + "webpack-cli": "^3.3.11" }, "resolutions": { "**/set-value": "^2.0.1" diff --git a/src/Components/Web.JS/src/BinaryDecoder.ts b/src/Components/Web.JS/src/BinaryDecoder.ts new file mode 100644 index 0000000000000000000000000000000000000000..ddf0a768d6a26e73367f9494729e67245dea3b01 --- /dev/null +++ b/src/Components/Web.JS/src/BinaryDecoder.ts @@ -0,0 +1,47 @@ +const uint64HighPartShift = Math.pow(2, 32); +const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER + +export function readInt32LE(buffer: Uint8Array, position: number): any { + return (buffer[position]) + | (buffer[position + 1] << 8) + | (buffer[position + 2] << 16) + | (buffer[position + 3] << 24); +} + +export function readUint32LE(buffer: Uint8Array, position: number): any { + return (buffer[position]) + + (buffer[position + 1] << 8) + + (buffer[position + 2] << 16) + + ((buffer[position + 3] << 24) >>> 0); // The >>> 0 coerces the value to unsigned +} + +export function readUint64LE(buffer: Uint8Array, position: number): any { + // This cannot be done using bit-shift operators in JavaScript, because + // those all implicitly convert to int32 + const highPart = readUint32LE(buffer, position + 4); + if (highPart > maxSafeNumberHighPart) { + throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`); + } + + return (highPart * uint64HighPartShift) + readUint32LE(buffer, position); +} + +export function readLEB128(buffer: Uint8Array, position: number) { + let result = 0; + let shift = 0; + for (let index = 0; index < 4; index++) { + const byte = buffer[position + index]; + result |= (byte & 127) << shift; + if (byte < 128) { + break; + } + shift += 7; + } + return result; +} + +export function numLEB128Bytes(value: number) { + return value < 128 ? 1 + : value < 16384 ? 2 + : value < 2097152 ? 3 : 4; +} \ No newline at end of file diff --git a/src/Components/Web.JS/src/Boot.Server.ts b/src/Components/Web.JS/src/Boot.Server.ts index 4ea227247c0d1a0ebd9008ae84874e52ee103c58..caed30b9008b9a4bad4a9e241f0503546456198d 100644 --- a/src/Components/Web.JS/src/Boot.Server.ts +++ b/src/Components/Web.JS/src/Boot.Server.ts @@ -9,14 +9,14 @@ import { ConsoleLogger } from './Platform/Logging/Loggers'; import { LogLevel, Logger } from './Platform/Logging/Logger'; import { discoverComponents, CircuitDescriptor } from './Platform/Circuits/CircuitManager'; import { setEventDispatcher } from './Rendering/RendererEventDispatcher'; -import { resolveOptions, BlazorOptions } from './Platform/Circuits/BlazorOptions'; +import { resolveOptions, CircuitStartOptions } from './Platform/Circuits/CircuitStartOptions'; import { DefaultReconnectionHandler } from './Platform/Circuits/DefaultReconnectionHandler'; import { attachRootComponentToLogicalElement } from './Rendering/Renderer'; let renderingFailed = false; let started = false; -async function boot(userOptions?: Partial<BlazorOptions>): Promise<void> { +async function boot(userOptions?: Partial<CircuitStartOptions>): Promise<void> { if (started) { throw new Error('Blazor has already started.'); } @@ -72,7 +72,7 @@ async function boot(userOptions?: Partial<BlazorOptions>): Promise<void> { logger.log(LogLevel.Information, 'Blazor server-side application started.'); } -async function initializeConnection(options: BlazorOptions, logger: Logger, circuit: CircuitDescriptor): Promise<signalR.HubConnection> { +async function initializeConnection(options: CircuitStartOptions, logger: Logger, circuit: CircuitDescriptor): Promise<signalR.HubConnection> { const hubProtocol = new MessagePackHubProtocol(); (hubProtocol as unknown as { name: string }).name = 'blazorpack'; diff --git a/src/Components/Web.JS/src/Boot.WebAssembly.ts b/src/Components/Web.JS/src/Boot.WebAssembly.ts index 1a800983017f45a98e2f03e09a6bcb7901d22dcd..1353d0a4036fffa4197ed7959c1cff88c6253728 100644 --- a/src/Components/Web.JS/src/Boot.WebAssembly.ts +++ b/src/Components/Web.JS/src/Boot.WebAssembly.ts @@ -4,20 +4,24 @@ import * as Environment from './Environment'; import { monoPlatform } from './Platform/Mono/MonoPlatform'; import { renderBatch } from './Rendering/Renderer'; import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch'; -import { Pointer } from './Platform/Platform'; import { shouldAutoStart } from './BootCommon'; import { setEventDispatcher } from './Rendering/RendererEventDispatcher'; +import { WebAssemblyResourceLoader } from './Platform/WebAssemblyResourceLoader'; +import { WebAssemblyConfigLoader } from './Platform/WebAssemblyConfigLoader'; +import { BootConfigResult } from './Platform/BootConfig'; +import { Pointer } from './Platform/Platform'; +import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions'; let started = false; -async function boot(options?: any): Promise<void> { +async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> { if (started) { throw new Error('Blazor has already started.'); } started = true; - setEventDispatcher((eventDescriptor, eventArgs) => DotNet.invokeMethodAsync('Microsoft.AspNetCore.Blazor', 'DispatchEvent', eventDescriptor, JSON.stringify(eventArgs))); + setEventDispatcher((eventDescriptor, eventArgs) => DotNet.invokeMethodAsync('Microsoft.AspNetCore.Components.WebAssembly', 'DispatchEvent', eventDescriptor, JSON.stringify(eventArgs))); // Configure environment for execution under Mono WebAssembly with shared-memory rendering const platform = Environment.setPlatform(monoPlatform); @@ -29,51 +33,39 @@ async function boot(options?: any): Promise<void> { // Configure navigation via JS Interop window['Blazor']._internal.navigationManager.listenForNavigationEvents(async (uri: string, intercepted: boolean): Promise<void> => { await DotNet.invokeMethodAsync( - 'Microsoft.AspNetCore.Blazor', + 'Microsoft.AspNetCore.Components.WebAssembly', 'NotifyLocationChanged', uri, intercepted ); }); - // Fetch the boot JSON file - const bootConfig = await fetchBootConfigAsync(); - - if (!bootConfig.linkerEnabled) { - console.info('Blazor is running in dev mode without IL stripping. To make the bundle size significantly smaller, publish the application or see https://go.microsoft.com/fwlink/?linkid=870414'); - } + // Fetch the resources and prepare the Mono runtime + const bootConfigResult = await BootConfigResult.initAsync(); - // Determine the URLs of the assemblies we want to load, then begin fetching them all - const loadAssemblyUrls = bootConfig.assemblies - .map(filename => `_framework/_bin/${filename}`); + const [resourceLoader] = await Promise.all([ + WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, options || {}), + WebAssemblyConfigLoader.initAsync(bootConfigResult)]); try { - await platform.start(loadAssemblyUrls); + await platform.start(resourceLoader); } catch (ex) { throw new Error(`Failed to start platform. Reason: ${ex}`); } // Start up the application - platform.callEntryPoint(bootConfig.entryAssembly); -} - -async function fetchBootConfigAsync() { - // Later we might make the location of this configurable (e.g., as an attribute on the <script> - // element that's importing this file), but currently there isn't a use case for that. - const bootConfigResponse = await fetch('_framework/blazor.boot.json', { method: 'Get', credentials: 'include' }); - return bootConfigResponse.json() as Promise<BootJsonData>; -} - -// Keep in sync with BootJsonData in Microsoft.AspNetCore.Blazor.Build -interface BootJsonData { - entryAssembly: string; - assemblies: string[]; - linkerEnabled: boolean; + platform.callEntryPoint(resourceLoader.bootConfig.entryAssembly); } window['Blazor'].start = boot; if (shouldAutoStart()) { boot().catch(error => { - Module.printErr(error); // Logs it, and causes the error UI to appear + if (typeof Module !== 'undefined' && Module.printErr) { + // Logs it, and causes the error UI to appear + Module.printErr(error); + } else { + // The error must have happened so early we didn't yet set up the error UI, so just log to console + console.error(error); + } }); } diff --git a/src/Components/Web.JS/src/Platform/BootConfig.ts b/src/Components/Web.JS/src/Platform/BootConfig.ts new file mode 100644 index 0000000000000000000000000000000000000000..d6bb4b01b0da2bd57463bd3904f86913f23c9e7c --- /dev/null +++ b/src/Components/Web.JS/src/Platform/BootConfig.ts @@ -0,0 +1,38 @@ +export class BootConfigResult { + private constructor(public bootConfig: BootJsonData, public applicationEnvironment: string) { + } + + static async initAsync(): Promise<BootConfigResult> { + const bootConfigResponse = await fetch('_framework/blazor.boot.json', { + method: 'GET', + credentials: 'include', + cache: 'no-cache' + }); + + // While we can expect an ASP.NET Core hosted application to include the environment, other + // hosts may not. Assume 'Production' in the absenc of any specified value. + const applicationEnvironment = bootConfigResponse.headers.get('Blazor-Environment') || 'Production'; + const bootConfig: BootJsonData = await bootConfigResponse.json(); + + return new BootConfigResult(bootConfig, applicationEnvironment); + }; +} + +// Keep in sync with bootJsonData in Microsoft.AspNetCore.Components.WebAssembly.Build +export interface BootJsonData { + readonly entryAssembly: string; + readonly resources: ResourceGroups; + readonly debugBuild: boolean; + readonly linkerEnabled: boolean; + readonly cacheBootResources: boolean; + readonly config: string[]; +} + +export interface ResourceGroups { + readonly assembly: ResourceList; + readonly pdb?: ResourceList; + readonly runtime: ResourceList; + readonly satelliteResources?: { [cultureName: string] : ResourceList }; +} + +export type ResourceList = { [name: string]: string }; diff --git a/src/Components/Web.JS/src/Platform/Circuits/BlazorOptions.ts b/src/Components/Web.JS/src/Platform/Circuits/CircuitStartOptions.ts similarity index 85% rename from src/Components/Web.JS/src/Platform/Circuits/BlazorOptions.ts rename to src/Components/Web.JS/src/Platform/Circuits/CircuitStartOptions.ts index 10d0c9a357347d2d0fe461869b498a1e7b2acc30..f83753e10dc0b63acc7c3c7b5c7f1524128bde81 100644 --- a/src/Components/Web.JS/src/Platform/Circuits/BlazorOptions.ts +++ b/src/Components/Web.JS/src/Platform/Circuits/CircuitStartOptions.ts @@ -1,13 +1,13 @@ import { LogLevel } from '../Logging/Logger'; -export interface BlazorOptions { +export interface CircuitStartOptions { configureSignalR: (builder: signalR.HubConnectionBuilder) => void; logLevel: LogLevel; reconnectionOptions: ReconnectionOptions; reconnectionHandler?: ReconnectionHandler; } -export function resolveOptions(userOptions?: Partial<BlazorOptions>): BlazorOptions { +export function resolveOptions(userOptions?: Partial<CircuitStartOptions>): CircuitStartOptions { const result = { ...defaultOptions, ...userOptions }; // The spread operator can't be used for a deep merge, so do the same for subproperties @@ -29,7 +29,7 @@ export interface ReconnectionHandler { onConnectionUp(): void; } -const defaultOptions: BlazorOptions = { +const defaultOptions: CircuitStartOptions = { configureSignalR: (_) => { }, logLevel: LogLevel.Warning, reconnectionOptions: { diff --git a/src/Components/Web.JS/src/Platform/Circuits/DefaultReconnectionHandler.ts b/src/Components/Web.JS/src/Platform/Circuits/DefaultReconnectionHandler.ts index f1e2848f74b481f2358ac035844def67ad83d3d5..b017bf5da98a75f690157f1d49a44645fe719dfe 100644 --- a/src/Components/Web.JS/src/Platform/Circuits/DefaultReconnectionHandler.ts +++ b/src/Components/Web.JS/src/Platform/Circuits/DefaultReconnectionHandler.ts @@ -1,4 +1,4 @@ -import { ReconnectionHandler, ReconnectionOptions } from './BlazorOptions'; +import { ReconnectionHandler, ReconnectionOptions } from './CircuitStartOptions'; import { ReconnectDisplay } from './ReconnectDisplay'; import { DefaultReconnectDisplay } from './DefaultReconnectDisplay'; import { UserSpecifiedDisplay } from './UserSpecifiedDisplay'; diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoDebugger.ts b/src/Components/Web.JS/src/Platform/Mono/MonoDebugger.ts index e16ec7e19534a3f40c908192796e73d0b74cd6f4..73c8dbce163529ca047da7113175f95c56af895e 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoDebugger.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoDebugger.ts @@ -1,4 +1,4 @@ -import { getAssemblyNameFromUrl, getFileNameFromUrl } from '../Url'; +import { WebAssemblyResourceLoader } from '../WebAssemblyResourceLoader'; const currentBrowserIsChrome = (window as any).chrome && navigator.userAgent.indexOf('Edge') < 0; // Edge pretends to be Chrome @@ -9,9 +9,8 @@ export function hasDebuggingEnabled() { return hasReferencedPdbs && currentBrowserIsChrome; } -export function attachDebuggerHotkey(loadAssemblyUrls: string[]) { - hasReferencedPdbs = loadAssemblyUrls - .some(url => /\.pdb$/.test(getFileNameFromUrl(url))); +export function attachDebuggerHotkey(resourceLoader: WebAssemblyResourceLoader) { + hasReferencedPdbs = !!resourceLoader.bootConfig.resources.pdb; // Use the combination shift+alt+D because it isn't used by the major browsers // for anything else by default @@ -26,7 +25,7 @@ export function attachDebuggerHotkey(loadAssemblyUrls: string[]) { if (!hasReferencedPdbs) { console.error('Cannot start debugging, because the application was not compiled with debugging enabled.'); } else if (!currentBrowserIsChrome) { - console.error('Currently, only Edge(Chromium) or Chrome is supported for debugging.'); + console.error('Currently, only Microsoft Edge (80+), or Google Chrome, are supported for debugging.'); } else { launchDebugger(); } diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts index 321a708f579d4d653588e01b3d5fafb6ade344b4..3387db38b3b8fbf0040a9998aead6777af24e9ca 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts @@ -1,17 +1,38 @@ -import { System_Object, System_String, System_Array, Pointer, Platform } from '../Platform'; -import { getFileNameFromUrl } from '../Url'; import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger'; import { showErrorNotification } from '../../BootErrors'; +import { WebAssemblyResourceLoader, LoadingResource } from '../WebAssemblyResourceLoader'; +import { Platform, System_Array, Pointer, System_Object, System_String } from '../Platform'; +import { loadTimezoneData } from './TimezoneDataFile'; +import { WebAssemblyBootResourceType } from '../WebAssemblyStartOptions'; -let mono_string_get_utf8: (managedString: System_String) => Mono.Utf8Ptr; +let mono_string_get_utf8: (managedString: System_String) => Pointer; +let mono_wasm_add_assembly: (name: string, heapAddress: number, length: number) => void; const appBinDirName = 'appBinDir'; const uint64HighOrderShift = Math.pow(2, 32); const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER +// Memory access helpers +// The implementations are exactly equivalent to what the global getValue(addr, type) function does, +// except without having to parse the 'type' parameter, and with less risk of mistakes at the call site +function getValueI16(ptr: number) { return Module.HEAP16[ptr >> 1]; } +function getValueI32(ptr: number) { return Module.HEAP32[ptr >> 2]; } +function getValueFloat(ptr: number) { return Module.HEAPF32[ptr >> 2]; } +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. + const heapU32Index = ptr >> 2; + const highPart = Module.HEAPU32[heapU32Index + 1]; + if (highPart > maxSafeNumberHighPart) { + throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`); + } + + return (highPart * uint64HighOrderShift) + Module.HEAPU32[heapU32Index]; +} + export const monoPlatform: Platform = { - start: function start(loadAssemblyUrls: string[]) { + start: function start(resourceLoader: WebAssemblyResourceLoader) { return new Promise<void>((resolve, reject) => { - attachDebuggerHotkey(loadAssemblyUrls); + attachDebuggerHotkey(resourceLoader); // dotnet.js assumes the existence of this window['Browser'] = { @@ -22,8 +43,8 @@ export const monoPlatform: Platform = { // For compatibility with macOS Catalina, we have to assign a temporary value to window.Module // before we start loading the WebAssembly files addGlobalModuleScriptTagsToDocument(() => { - window['Module'] = createEmscriptenModuleInstance(loadAssemblyUrls, resolve, reject); - addScriptTagsToDocument(); + window['Module'] = createEmscriptenModuleInstance(resourceLoader, resolve, reject); + addScriptTagsToDocument(resourceLoader); }); }); }, @@ -35,30 +56,19 @@ export const monoPlatform: Platform = { // In the future, we might want Blazor.start to return a Promise<Promise<value>>, where the // outer promise reflects the startup process, and the inner one reflects the possibly-async // .NET entrypoint method. - const invokeEntrypoint = bindStaticMethod('Microsoft.AspNetCore.Blazor', 'Microsoft.AspNetCore.Blazor.Hosting.EntrypointInvoker', 'InvokeEntrypoint'); + const invokeEntrypoint = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Hosting.EntrypointInvoker', 'InvokeEntrypoint'); // Note we're passing in null because passing arrays is problematic until https://github.com/mono/mono/issues/18245 is resolved. invokeEntrypoint(assemblyName, null); }, - toJavaScriptString: function toJavaScriptString(managedString: System_String) { - // Comments from original Mono sample: - // FIXME this is wastefull, we could remove the temp malloc by going the UTF16 route - // FIXME this is unsafe, cuz raw objects could be GC'd. - - const utf8 = mono_string_get_utf8(managedString); - const res = Module.UTF8ToString(utf8); - Module._free(utf8 as any); - return res; - }, - toUint8Array: function toUint8Array(array: System_Array<any>): Uint8Array { const dataPtr = getArrayDataPointer(array); - const length = Module.getValue(dataPtr, 'i32'); + const length = getValueI32(dataPtr); return new Uint8Array(Module.HEAPU8.buffer, dataPtr + 4, length); }, getArrayLength: function getArrayLength(array: System_Array<any>): number { - return Module.getValue(getArrayDataPointer(array), 'i32'); + return getValueI32(getArrayDataPointer(array)); }, getArrayEntryPtr: function getArrayEntryPtr<TPtr extends Pointer>(array: System_Array<TPtr>, index: number, itemSize: number): TPtr { @@ -73,37 +83,42 @@ export const monoPlatform: Platform = { }, readInt16Field: function readHeapInt16(baseAddress: Pointer, fieldOffset?: number): number { - return Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'i16'); + return getValueI16((baseAddress as any as number) + (fieldOffset || 0)); }, readInt32Field: function readHeapInt32(baseAddress: Pointer, fieldOffset?: number): number { - return Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'i32'); + return getValueI32((baseAddress as any as number) + (fieldOffset || 0)); }, readUint64Field: function readHeapUint64(baseAddress: Pointer, fieldOffset?: number): number { - // 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. - const address = (baseAddress as any as number) + (fieldOffset || 0); - const heapU32Index = address >> 2; - const highPart = Module.HEAPU32[heapU32Index + 1]; - if (highPart > maxSafeNumberHighPart) { - throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`); - } - - return (highPart * uint64HighOrderShift) + Module.HEAPU32[heapU32Index]; + return getValueU64((baseAddress as any as number) + (fieldOffset || 0)); }, readFloatField: function readHeapFloat(baseAddress: Pointer, fieldOffset?: number): number { - return Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'float'); + return getValueFloat((baseAddress as any as number) + (fieldOffset || 0)); }, readObjectField: function readHeapObject<T extends System_Object>(baseAddress: Pointer, fieldOffset?: number): T { - return Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'i32') as any as T; + return getValueI32((baseAddress as any as number) + (fieldOffset || 0)) as any as T; }, - readStringField: function readHeapObject(baseAddress: Pointer, fieldOffset?: number): string | null { - const fieldValue = Module.getValue((baseAddress as any as number) + (fieldOffset || 0), 'i32'); - return fieldValue === 0 ? null : monoPlatform.toJavaScriptString(fieldValue as any as System_String); + readStringField: function readHeapObject(baseAddress: Pointer, fieldOffset?: number, readBoolValueAsString?: boolean): string | null { + const fieldValue = getValueI32((baseAddress as any as number) + (fieldOffset || 0)); + if (fieldValue === 0) { + return null; + } + + if (readBoolValueAsString) { + // Some fields are stored as a union of bool | string | null values, but need to read as a string. + // If the stored value is a bool, the behavior we want is empty string ('') for true, or null for false. + const unboxedValue = BINDING.unbox_mono_obj(fieldValue as any as System_Object); + if (typeof (unboxedValue) === 'boolean') { + return unboxedValue ? '' : null; + } + return unboxedValue; + } + + return BINDING.conv_string(fieldValue as any as System_String); }, readStructField: function readStructField<T extends Pointer>(baseAddress: Pointer, fieldOffset?: number): T { @@ -111,15 +126,43 @@ export const monoPlatform: Platform = { }, }; -function addScriptTagsToDocument() { +function addScriptTagsToDocument(resourceLoader: WebAssemblyResourceLoader) { const browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate; if (!browserSupportsNativeWebAssembly) { throw new Error('This browser does not support WebAssembly.'); } + // The dotnet.*.js file has a version or hash in its name as a form of cache-busting. This is needed + // because it's the only part of the loading process that can't use cache:'no-cache' (because it's + // not a 'fetch') and isn't controllable by the developer (so they can't put in their own cache-busting + // querystring). So, to find out the exact URL we have to search the boot manifest. + const dotnetJsResourceName = Object + .keys(resourceLoader.bootConfig.resources.runtime) + .filter(n => n.startsWith('dotnet.') && n.endsWith('.js'))[0]; + const dotnetJsContentHash = resourceLoader.bootConfig.resources.runtime[dotnetJsResourceName]; const scriptElem = document.createElement('script'); - scriptElem.src = '_framework/wasm/dotnet.js'; + scriptElem.src = `_framework/wasm/${dotnetJsResourceName}`; scriptElem.defer = true; + + // For consistency with WebAssemblyResourceLoader, we only enforce SRI if caching is allowed + if (resourceLoader.bootConfig.cacheBootResources) { + scriptElem.integrity = dotnetJsContentHash; + scriptElem.crossOrigin = 'anonymous'; + } + + // Allow overriding the URI from which the dotnet.*.js file is loaded + if (resourceLoader.startOptions.loadBootResource) { + const resourceType: WebAssemblyBootResourceType = 'dotnetjs'; + const customSrc = resourceLoader.startOptions.loadBootResource( + resourceType, dotnetJsResourceName, scriptElem.src, dotnetJsContentHash); + if (typeof(customSrc) === 'string') { + scriptElem.src = customSrc; + } else if (customSrc) { + // Since we must load this via a <script> tag, it's only valid to supply a URI (and not a Request, say) + throw new Error(`For a ${resourceType} resource, custom loaders must supply a URI string.`); + } + } + document.body.appendChild(scriptElem); } @@ -140,79 +183,151 @@ function addGlobalModuleScriptTagsToDocument(callback: () => void) { document.body.appendChild(scriptElem); } -function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: () => void, onError: (reason?: any) => void) { - const module = {} as typeof Module; - const wasmBinaryFile = '_framework/wasm/dotnet.wasm'; +function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoader, onReady: () => void, onError: (reason?: any) => void) { + const resources = resourceLoader.bootConfig.resources; + const module = (window['Module'] || { }) as typeof Module; const suppressMessages = ['DEBUGGING ENABLED']; - module.print = line => (suppressMessages.indexOf(line) < 0 && console.log(`WASM: ${line}`)); + module.print = line => (suppressMessages.indexOf(line) < 0 && console.log(line)); module.printErr = line => { - console.error(`WASM: ${line}`); + // If anything writes to stderr, treat it as a critical exception. The underlying runtime writes + // to stderr if a truly critical problem occurs outside .NET code. Note that .NET unhandled + // exceptions also reach this, but via a different code path - see dotNetCriticalError below. + console.error(line); showErrorNotification(); }; - module.preRun = []; - module.postRun = []; - module.preloadPlugins = []; - - module.locateFile = fileName => { - switch (fileName) { - case 'dotnet.wasm': return wasmBinaryFile; - default: return fileName; - } + module.preRun = module.preRun || []; + module.postRun = module.postRun || []; + (module as any).preloadPlugins = []; + + // Begin loading the .dll/.pdb/.wasm files, but don't block here. Let other loading processes run in parallel. + const dotnetWasmResourceName = 'dotnet.wasm'; + const assembliesBeingLoaded = resourceLoader.loadResources(resources.assembly, filename => `_framework/_bin/${filename}`, 'assembly'); + const pdbsBeingLoaded = resourceLoader.loadResources(resources.pdb || {}, filename => `_framework/_bin/${filename}`, 'pdb'); + const wasmBeingLoaded = resourceLoader.loadResource( + /* name */ dotnetWasmResourceName, + /* url */ `_framework/wasm/${dotnetWasmResourceName}`, + /* hash */ resourceLoader.bootConfig.resources.runtime[dotnetWasmResourceName], + /* type */ 'dotnetwasm'); + + const dotnetTimeZoneResourceName = 'dotnet.timezones.dat'; + let timeZoneResource: LoadingResource | undefined; + if (resourceLoader.bootConfig.resources.runtime.hasOwnProperty(dotnetTimeZoneResourceName)) { + timeZoneResource = resourceLoader.loadResource( + dotnetTimeZoneResourceName, + `_framework/wasm/${dotnetTimeZoneResourceName}`, + resourceLoader.bootConfig.resources.runtime[dotnetTimeZoneResourceName], + 'timezonedata'); + } + + // Override the mechanism for fetching the main wasm file so we can connect it to our cache + module.instantiateWasm = (imports, successCallback): Emscripten.WebAssemblyExports => { + (async () => { + let compiledInstance: WebAssembly.Instance; + try { + const dotnetWasmResource = await wasmBeingLoaded; + compiledInstance = await compileWasmModule(dotnetWasmResource, imports); + } catch (ex) { + module.printErr(ex); + throw ex; + } + successCallback(compiledInstance); + })(); + return []; // No exports }; module.preRun.push(() => { // By now, emscripten should be initialised enough that we can capture these methods for later use - const mono_wasm_add_assembly = Module.cwrap('mono_wasm_add_assembly', null, [ - 'string', - 'number', - 'number', - ]); + mono_wasm_add_assembly = cwrap('mono_wasm_add_assembly', null, ['string', 'number', 'number']); + mono_string_get_utf8 = cwrap('mono_wasm_string_get_utf8', 'number', ['number']); + MONO.loaded_files = []; - mono_string_get_utf8 = Module.cwrap('mono_wasm_string_get_utf8', 'number', ['number']); + if (timeZoneResource) { + loadTimezone(timeZoneResource); + } - MONO.loaded_files = []; + // Fetch the assemblies and PDBs in the background, telling Mono to wait until they are loaded + // Mono requires the assembly filenames to have a '.dll' extension, so supply such names regardless + // of the extensions in the URLs. This allows loading assemblies with arbitrary filenames. + assembliesBeingLoaded.forEach(r => addResourceAsAssembly(r, changeExtension(r.name, '.dll'))); + pdbsBeingLoaded.forEach(r => addResourceAsAssembly(r, r.name)); - loadAssemblyUrls.forEach(url => { - const filename = getFileNameFromUrl(url); - const runDependencyId = `blazor:${filename}`; - addRunDependency(runDependencyId); - asyncLoad(url).then( - data => { - const heapAddress = Module._malloc(data.length); - const heapMemory = new Uint8Array(Module.HEAPU8.buffer, heapAddress, data.length); - heapMemory.set(data); - mono_wasm_add_assembly(filename, heapAddress, data.length); - MONO.loaded_files.push(toAbsoluteUrl(url)); - removeRunDependency(runDependencyId); - }, - errorInfo => { - // If it's a 404 on a .pdb, we don't want to block the app from starting up. - // We'll just skip that file and continue (though the 404 is logged in the console). - // This happens if you build a Debug build but then run in Production environment. - const isPdb404 = errorInfo instanceof XMLHttpRequest - && errorInfo.status === 404 - && filename.match(/\.pdb$/); - if (!isPdb404) { - onError(errorInfo); + window['Blazor']._internal.dotNetCriticalError = (message: System_String) => { + module.printErr(BINDING.conv_string(message) || '(null)'); + }; + + // Wire-up callbacks for satellite assemblies. Blazor will call these as part of the application + // startup sequence to load satellite assemblies for the application's culture. + window['Blazor']._internal.getSatelliteAssemblies = (culturesToLoadDotNetArray: System_Array<System_String>) : System_Object => { + const culturesToLoad = BINDING.mono_array_to_js_array<System_String, string>(culturesToLoadDotNetArray); + const satelliteResources = resourceLoader.bootConfig.resources.satelliteResources; + + if (satelliteResources) { + const resourcePromises = Promise.all(culturesToLoad + .filter(culture => satelliteResources.hasOwnProperty(culture)) + .map(culture => resourceLoader.loadResources(satelliteResources[culture], fileName => `_framework/_bin/${fileName}`, 'assembly')) + .reduce((previous, next) => previous.concat(next), new Array<LoadingResource>()) + .map(async resource => (await resource.response).arrayBuffer())); + + return BINDING.js_to_mono_obj( + resourcePromises.then(resourcesToLoad => { + if (resourcesToLoad.length) { + window['Blazor']._internal.readSatelliteAssemblies = () => { + const array = BINDING.mono_obj_array_new(resourcesToLoad.length); + for (var i = 0; i < resourcesToLoad.length; i++) { + BINDING.mono_obj_array_set(array, i, BINDING.js_typed_array_to_array(new Uint8Array(resourcesToLoad[i]))); + } + return array; + }; } - removeRunDependency(runDependencyId); - } - ); - }); + + return resourcesToLoad.length; + })); + } + return BINDING.js_to_mono_obj(Promise.resolve(0)); + } }); module.postRun.push(() => { + if (resourceLoader.bootConfig.debugBuild && resourceLoader.bootConfig.cacheBootResources) { + resourceLoader.logToConsole(); + } + resourceLoader.purgeUnusedCacheEntriesAsync(); // Don't await - it's fine to run in background + MONO.mono_wasm_setenv("MONO_URI_DOTNETRELATIVEORABSOLUTE", "true"); - const load_runtime = Module.cwrap('mono_wasm_load_runtime', null, ['string', 'number']); - load_runtime(appBinDirName, hasDebuggingEnabled() ? 1 : 0); - MONO.mono_wasm_runtime_is_ready = true; + const load_runtime = cwrap('mono_wasm_load_runtime', null, ['string', 'number']); + // -1 enables debugging with logging disabled. 0 disables debugging entirely. + load_runtime(appBinDirName, hasDebuggingEnabled() ? -1 : 0); + MONO.mono_wasm_runtime_ready (); attachInteropInvoker(); onReady(); }); return module; + + async function addResourceAsAssembly(dependency: LoadingResource, loadAsName: string) { + const runDependencyId = `blazor:${dependency.name}`; + addRunDependency(runDependencyId); + + try { + // Wait for the data to be loaded and verified + const dataBuffer = await dependency.response.then(r => r.arrayBuffer()); + + // Load it into the Mono runtime + const data = new Uint8Array(dataBuffer); + const heapAddress = Module._malloc(data.length); + const heapMemory = new Uint8Array(Module.HEAPU8.buffer, heapAddress, data.length); + heapMemory.set(data); + mono_wasm_add_assembly(loadAsName, heapAddress, data.length); + MONO.loaded_files.push(toAbsoluteUrl(dependency.url)); + } catch (errorInfo) { + onError(errorInfo); + return; + } + + removeRunDependency(runDependencyId); + } } const anchorTagForAbsoluteUrlConversions = document.createElement('a'); @@ -221,38 +336,20 @@ function toAbsoluteUrl(possiblyRelativeUrl: string) { return anchorTagForAbsoluteUrlConversions.href; } -function asyncLoad(url: string) { - return new Promise<Uint8Array>((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open('GET', url, /* async: */ true); - xhr.responseType = 'arraybuffer'; - xhr.onload = function xhr_onload() { - if (xhr.status == 200 || xhr.status == 0 && xhr.response) { - const asm = new Uint8Array(xhr.response); - resolve(asm); - } else { - reject(xhr); - } - }; - xhr.onerror = reject; - xhr.send(undefined); - }); -} - function getArrayDataPointer<T>(array: System_Array<T>): number { return <number><any>array + 12; // First byte from here is length, then following bytes are entries } -function bindStaticMethod(assembly: string, typeName: string, method: string) : (...args: any[]) => any { +function bindStaticMethod(assembly: string, typeName: string, method: string) { // Fully qualified name looks like this: "[debugger-test] Math:IntAdd" const fqn = `[${assembly}] ${typeName}:${method}`; - return Module.mono_bind_static_method(fqn); + return BINDING.bind_static_method(fqn); } function attachInteropInvoker(): void { - const dotNetDispatcherInvokeMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'InvokeDotNet'); - const dotNetDispatcherBeginInvokeMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'BeginInvokeDotNet'); - const dotNetDispatcherEndInvokeJSMethodHandle = bindStaticMethod('Mono.WebAssembly.Interop', 'Mono.WebAssembly.Interop.MonoWebAssemblyJSRuntime', 'EndInvokeJS'); + const dotNetDispatcherInvokeMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'InvokeDotNet'); + const dotNetDispatcherBeginInvokeMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'BeginInvokeDotNet'); + const dotNetDispatcherEndInvokeJSMethodHandle = bindStaticMethod('Microsoft.AspNetCore.Components.WebAssembly', 'Microsoft.AspNetCore.Components.WebAssembly.Services.DefaultWebAssemblyJSRuntime', 'EndInvokeJS'); DotNet.attachDispatcher({ beginInvokeDotNetFromJS: (callId: number, assemblyName: string | null, methodIdentifier: string, dotNetObjectId: any | null, argsJson: string): void => { @@ -287,3 +384,45 @@ function attachInteropInvoker(): void { }, }); } + +async function loadTimezone(timeZoneResource: LoadingResource) : Promise<void> { + const runDependencyId = `blazor:timezonedata`; + addRunDependency(runDependencyId); + + const request = await timeZoneResource.response; + const arrayBuffer = await request.arrayBuffer(); + loadTimezoneData(arrayBuffer) + + removeRunDependency(runDependencyId); +} + +async function compileWasmModule(wasmResource: LoadingResource, imports: any): Promise<WebAssembly.Instance> { + // This is the same logic as used in emscripten's generated js. We can't use emscripten's js because + // it doesn't provide any method for supplying a custom response provider, and we want to integrate + // with our resource loader cache. + + if (typeof WebAssembly['instantiateStreaming'] === 'function') { + try { + const streamingResult = await WebAssembly['instantiateStreaming'](wasmResource.response, imports); + return streamingResult.instance; + } + catch (ex) { + console.info('Streaming compilation failed. Falling back to ArrayBuffer instantiation. ', ex); + } + } + + // If that's not available or fails (e.g., due to incorrect content-type header), + // fall back to ArrayBuffer instantiation + const arrayBuffer = await wasmResource.response.then(r => r.arrayBuffer()); + const arrayBufferResult = await WebAssembly.instantiate(arrayBuffer, imports); + return arrayBufferResult.instance; +} + +function changeExtension(filename: string, newExtensionWithLeadingDot: string) { + const lastDotIndex = filename.lastIndexOf('.'); + if (lastDotIndex < 0) { + throw new Error(`No extension to replace in '${filename}'`); + } + + return filename.substr(0, lastDotIndex) + newExtensionWithLeadingDot; +} diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoTypes.d.ts b/src/Components/Web.JS/src/Platform/Mono/MonoTypes.d.ts deleted file mode 100644 index 7d2f5c23bf036f0c783cb06a5d81a7de1cdfd6fb..0000000000000000000000000000000000000000 --- a/src/Components/Web.JS/src/Platform/Mono/MonoTypes.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -declare namespace Module { - function UTF8ToString(utf8: Mono.Utf8Ptr): string; - var preloadPlugins: any[]; - - function stackSave(): Mono.StackSaveHandle; - function stackAlloc(length: number): number; - function stackRestore(handle: Mono.StackSaveHandle): void; - - // These should probably be in @types/emscripten - function FS_createPath(parent, path, canRead, canWrite); - function FS_createDataFile(parent, name, data, canRead, canWrite, canOwn); - - function mono_bind_static_method(fqn: string): BoundStaticMethod; -} - -// Emscripten declares these globals -declare const addRunDependency: any; -declare const removeRunDependency: any; - -declare namespace Mono { - interface Utf8Ptr { Utf8Ptr__DO_NOT_IMPLEMENT: any } - interface StackSaveHandle { StackSaveHandle__DO_NOT_IMPLEMENT: any } -} - -// Mono uses this global to hang various debugging-related items on -declare namespace MONO { - var loaded_files: string[]; - var mono_wasm_runtime_is_ready: boolean; - function mono_wasm_setenv (name: string, value: string): void; -} - -// mono_bind_static_method allows arbitrary JS data types to be sent over the wire. However we are -// artifically limiting it to a subset of types that we actually use. -declare type BoundStaticMethod = (...args: (string | number | null)[]) => (string | number | null); diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoTypes.ts b/src/Components/Web.JS/src/Platform/Mono/MonoTypes.ts new file mode 100644 index 0000000000000000000000000000000000000000..6d833884303b7414dbecf1352483a7c62ee7af13 --- /dev/null +++ b/src/Components/Web.JS/src/Platform/Mono/MonoTypes.ts @@ -0,0 +1,28 @@ +import { Pointer, System_String, System_Array, System_Object } from '../Platform'; + +// Mono uses this global to hang various debugging-related items on + +declare interface MONO { + loaded_files: string[]; + mono_wasm_runtime_ready (): void; + mono_wasm_setenv (name: string, value: string): void; +} + +// Mono uses this global to hold low-level interop APIs +declare interface BINDING { + mono_obj_array_new(length: number): System_Array<System_Object>; + mono_obj_array_set(array: System_Array<System_Object>, index: Number, value: System_Object): void; + js_string_to_mono_string(jsString: string): System_String; + js_typed_array_to_array(array: Uint8Array): System_Object; + js_to_mono_obj(jsObject: any) : System_Object; + mono_array_to_js_array<TInput, TOutput>(array: System_Array<TInput>) : Array<TOutput>; + conv_string(dotnetString: System_String | null): string | null; + bind_static_method(fqn: string, signature?: string): Function; + unbox_mono_obj(object: System_Object): any; +} + +declare global { + var MONO: MONO; + var BINDING: BINDING; +} + diff --git a/src/Components/Web.JS/src/Platform/Mono/TimezoneDataFile.ts b/src/Components/Web.JS/src/Platform/Mono/TimezoneDataFile.ts new file mode 100644 index 0000000000000000000000000000000000000000..47076f4aae2c8509fd62f44462cb1a2ae486d627 --- /dev/null +++ b/src/Components/Web.JS/src/Platform/Mono/TimezoneDataFile.ts @@ -0,0 +1,43 @@ +import { readInt32LE } from "../../BinaryDecoder"; +import { decodeUtf8 } from "../../Utf8Decoder"; + +export function loadTimezoneData(arrayBuffer: ArrayBuffer) { + let remainingData = new Uint8Array(arrayBuffer); + + // The timezone file is generated by https://github.com/dotnet/blazor/tree/master/src/TimeZoneData. + // The file format of the TZ file look like so + // + // [4 - byte length of manifest] + // [json manifest] + // [data bytes] + // + // The json manifest is an array that looks like so: + // + // [...["America/Fort_Nelson",2249],["America/Glace_Bay",2206]..] + // + // where the first token in each array is the relative path of the file on disk, and the second is the + // length of the file. The starting offset of a file can be calculated using the lengths of all files + // that appear prior to it. + const manifestSize = readInt32LE(remainingData, 0); + remainingData = remainingData.slice(4); + const manifestContent = decodeUtf8(remainingData.slice(0, manifestSize)); + const manifest = JSON.parse(manifestContent) as ManifestEntry[]; + remainingData = remainingData.slice(manifestSize); + + // Create the folder structure + // /zoneinfo + // /zoneinfo/Africa + // /zoneinfo/Asia + // .. + Module['FS_createPath']('/', 'zoneinfo', true, true); + new Set(manifest.map(m => m[0].split('/')![0])).forEach(folder => + Module['FS_createPath']('/zoneinfo', folder, true, true)); + + for (const [name, length] of manifest) { + const bytes = remainingData.slice(0, length); + Module['FS_createDataFile'](`/zoneinfo/${name}`, null, bytes, true, true, true); + remainingData = remainingData.slice(length); + } + } + + type ManifestEntry = [string, number]; \ No newline at end of file diff --git a/src/Components/Web.JS/src/Platform/Platform.ts b/src/Components/Web.JS/src/Platform/Platform.ts index 8d5daf454a3f0637a752dd9f139a6cbbf1aa05cd..332c6b2d5fb237832c89ca6fd70ce33de8801ae3 100644 --- a/src/Components/Web.JS/src/Platform/Platform.ts +++ b/src/Components/Web.JS/src/Platform/Platform.ts @@ -1,9 +1,10 @@ +import { WebAssemblyResourceLoader } from './WebAssemblyResourceLoader'; + export interface Platform { - start(loadAssemblyUrls: string[]): Promise<void>; + start(resourceLoader: WebAssemblyResourceLoader): Promise<void>; callEntryPoint(assemblyName: string): void; - toJavaScriptString(dotNetString: System_String): string; toUint8Array(array: System_Array<any>): Uint8Array; getArrayLength(array: System_Array<any>): number; @@ -15,7 +16,7 @@ export interface Platform { readUint64Field(baseAddress: Pointer, fieldOffset?: number): number; readFloatField(baseAddress: Pointer, fieldOffset?: number): number; readObjectField<T extends System_Object>(baseAddress: Pointer, fieldOffset?: number): T; - readStringField(baseAddress: Pointer, fieldOffset?: number): string | null; + readStringField(baseAddress: Pointer, fieldOffset?: number, readBoolValueAsString?: boolean): string | null; readStructField<T extends Pointer>(baseAddress: Pointer, fieldOffset?: number): T; } diff --git a/src/Components/Web.JS/src/Platform/Url.ts b/src/Components/Web.JS/src/Platform/Url.ts deleted file mode 100644 index d8877124600ccef208c9dadd062377e4c4985357..0000000000000000000000000000000000000000 --- a/src/Components/Web.JS/src/Platform/Url.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function getFileNameFromUrl(url: string) { - // This could also be called "get last path segment from URL", but the primary - // use case is to extract things that look like filenames - const lastSegment = url.substring(url.lastIndexOf('/') + 1); - const queryStringStartPos = lastSegment.indexOf('?'); - return queryStringStartPos < 0 ? lastSegment : lastSegment.substring(0, queryStringStartPos); -} - -export function getAssemblyNameFromUrl(url: string) { - return getFileNameFromUrl(url).replace(/\.dll$/, ''); -} diff --git a/src/Components/Web.JS/src/Platform/WebAssemblyConfigLoader.ts b/src/Components/Web.JS/src/Platform/WebAssemblyConfigLoader.ts new file mode 100644 index 0000000000000000000000000000000000000000..646157383a215b6656ee65ef6d844c77da44ca0c --- /dev/null +++ b/src/Components/Web.JS/src/Platform/WebAssemblyConfigLoader.ts @@ -0,0 +1,28 @@ +import { BootConfigResult } from './BootConfig'; +import { System_String, System_Object } from './Platform'; + +export class WebAssemblyConfigLoader { + static async initAsync(bootConfigResult: BootConfigResult): Promise<void> { + window['Blazor']._internal.getApplicationEnvironment = () => BINDING.js_string_to_mono_string(bootConfigResult.applicationEnvironment); + + const configFiles = await Promise.all((bootConfigResult.bootConfig.config || []) + .filter(name => name === 'appsettings.json' || name === `appsettings.${bootConfigResult.applicationEnvironment}.json`) + .map(async name => ({ name, content: await getConfigBytes(name) }))); + + window['Blazor']._internal.getConfig = (dotNetFileName: System_String) : System_Object | undefined => { + const fileName = BINDING.conv_string(dotNetFileName); + const resolvedFile = configFiles.find(f => f.name === fileName); + return resolvedFile ? BINDING.js_typed_array_to_array(resolvedFile.content) : undefined; + }; + + async function getConfigBytes(file: string): Promise<Uint8Array> { + const response = await fetch(file, { + method: 'GET', + credentials: 'include', + cache: 'no-cache' + }); + + return new Uint8Array(await response.arrayBuffer()); + } + } +} diff --git a/src/Components/Web.JS/src/Platform/WebAssemblyResourceLoader.ts b/src/Components/Web.JS/src/Platform/WebAssemblyResourceLoader.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ab3364d5b7e10134dec2252d1e6f44b3fffadb0 --- /dev/null +++ b/src/Components/Web.JS/src/Platform/WebAssemblyResourceLoader.ts @@ -0,0 +1,198 @@ +import { toAbsoluteUri } from '../Services/NavigationManager'; +import { BootJsonData, ResourceList } from './BootConfig'; +import { WebAssemblyStartOptions, WebAssemblyBootResourceType } from './WebAssemblyStartOptions'; +const networkFetchCacheMode = 'no-cache'; + +export class WebAssemblyResourceLoader { + private usedCacheKeys: { [key: string]: boolean } = {}; + private networkLoads: { [name: string]: LoadLogEntry } = {}; + private cacheLoads: { [name: string]: LoadLogEntry } = {}; + + static async initAsync(bootConfig: BootJsonData, startOptions: Partial<WebAssemblyStartOptions>): Promise<WebAssemblyResourceLoader> { + const cache = await getCacheToUseIfEnabled(bootConfig); + return new WebAssemblyResourceLoader(bootConfig, cache, startOptions); + } + + constructor(readonly bootConfig: BootJsonData, readonly cacheIfUsed: Cache | null, readonly startOptions: Partial<WebAssemblyStartOptions>) { + } + + loadResources(resources: ResourceList, url: (name: string) => string, resourceType: WebAssemblyBootResourceType): LoadingResource[] { + return Object.keys(resources) + .map(name => this.loadResource(name, url(name), resources[name], resourceType)); + } + + loadResource(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): LoadingResource { + const response = this.cacheIfUsed + ? this.loadResourceWithCaching(this.cacheIfUsed, name, url, contentHash, resourceType) + : this.loadResourceWithoutCaching(name, url, contentHash, resourceType); + + return { name, url, response }; + } + + logToConsole() { + const cacheLoadsEntries = Object.values(this.cacheLoads); + const networkLoadsEntries = Object.values(this.networkLoads); + const cacheResponseBytes = countTotalBytes(cacheLoadsEntries); + const networkResponseBytes = countTotalBytes(networkLoadsEntries); + const totalResponseBytes = cacheResponseBytes + networkResponseBytes; + if (totalResponseBytes === 0) { + // We have no perf stats to display, likely because caching is not in use. + return; + } + + const linkerDisabledWarning = this.bootConfig.linkerEnabled ? '%c' : '\n%cThis application was built with linking (tree shaking) disabled. Published applications will be significantly smaller.'; + console.groupCollapsed(`%cblazor%c Loaded ${toDataSizeString(totalResponseBytes)} resources${linkerDisabledWarning}`, 'background: purple; color: white; padding: 1px 3px; border-radius: 3px;', 'font-weight: bold;', 'font-weight: normal;'); + + if (cacheLoadsEntries.length) { + console.groupCollapsed(`Loaded ${toDataSizeString(cacheResponseBytes)} resources from cache`); + console.table(this.cacheLoads); + console.groupEnd(); + } + + if (networkLoadsEntries.length) { + console.groupCollapsed(`Loaded ${toDataSizeString(networkResponseBytes)} resources from network`); + console.table(this.networkLoads); + console.groupEnd(); + } + + console.groupEnd(); + } + + async purgeUnusedCacheEntriesAsync() { + // We want to keep the cache small because, even though the browser will evict entries if it + // gets too big, we don't want to be considered problematic by the end user viewing storage stats + const cache = this.cacheIfUsed; + if (cache) { + const cachedRequests = await cache.keys(); + const deletionPromises = cachedRequests.map(async cachedRequest => { + if (!(cachedRequest.url in this.usedCacheKeys)) { + await cache.delete(cachedRequest); + } + }); + + await Promise.all(deletionPromises); + } + } + + private async loadResourceWithCaching(cache: Cache, name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType) { + // Since we are going to cache the response, we require there to be a content hash for integrity + // checking. We don't want to cache bad responses. There should always be a hash, because the build + // process generates this data. + if (!contentHash || contentHash.length === 0) { + throw new Error('Content hash is required'); + } + + const cacheKey = toAbsoluteUri(`${url}.${contentHash}`); + this.usedCacheKeys[cacheKey] = true; + + const cachedResponse = await cache.match(cacheKey); + if (cachedResponse) { + // It's in the cache. + const responseBytes = parseInt(cachedResponse.headers.get('content-length') || '0'); + this.cacheLoads[name] = { responseBytes }; + return cachedResponse; + } else { + // It's not in the cache. Fetch from network. + const networkResponse = await this.loadResourceWithoutCaching(name, url, contentHash, resourceType); + this.addToCacheAsync(cache, name, cacheKey, networkResponse); // Don't await - add to cache in background + return networkResponse; + } + } + + private loadResourceWithoutCaching(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): Promise<Response> { + // Allow developers to override how the resource is loaded + if (this.startOptions.loadBootResource) { + const customLoadResult = this.startOptions.loadBootResource(resourceType, name, url, contentHash); + if (customLoadResult instanceof Promise) { + // They are supplying an entire custom response, so just use that + return customLoadResult; + } else if (typeof customLoadResult === 'string') { + // They are supplying a custom URL, so use that with the default fetch behavior + url = customLoadResult; + } + } + + // Note that if cacheBootResources was explicitly disabled, we also bypass hash checking + // This is to give developers an easy opt-out from the entire caching/validation flow if + // there's anything they don't like about it. + return fetch(url, { + cache: networkFetchCacheMode, + integrity: this.bootConfig.cacheBootResources ? contentHash : undefined + }); + } + + private async addToCacheAsync(cache: Cache, name: string, cacheKey: string, response: Response) { + // We have to clone in order to put this in the cache *and* not prevent other code from + // reading the original response stream. + const responseData = await response.clone().arrayBuffer(); + + // Now is an ideal moment to capture the performance stats for the request, since it + // only just completed and is most likely to still be in the buffer. However this is + // only done on a 'best effort' basis. Even if we do receive an entry, some of its + // properties may be blanked out if it was a CORS request. + const performanceEntry = getPerformanceEntry(response.url); + const responseBytes = (performanceEntry && performanceEntry.encodedBodySize) || undefined; + this.networkLoads[name] = { responseBytes }; + + // Add to cache as a custom response object so we can track extra data such as responseBytes + // We can't rely on the server sending content-length (ASP.NET Core doesn't by default) + await cache.put(cacheKey, new Response(responseData, { + headers: { + 'content-type': response.headers.get('content-type') || '', + 'content-length': (responseBytes || response.headers.get('content-length') || '').toString() + } + })); + } +} + +async function getCacheToUseIfEnabled(bootConfig: BootJsonData): Promise<Cache | null> { + // caches will be undefined if we're running on an insecure origin (secure means https or localhost) + if (!bootConfig.cacheBootResources || typeof caches === 'undefined') { + return null; + } + + // Define a separate cache for each base href, so we're isolated from any other + // Blazor application running on the same origin. We need this so that we're free + // to purge from the cache anything we're not using and don't let it keep growing, + // since we don't want to be worst offenders for space usage. + const relativeBaseHref = document.baseURI.substring(document.location.origin.length); + const cacheName = `blazor-resources-${relativeBaseHref}`; + + try { + // There's a Chromium bug we need to be aware of here: the CacheStorage APIs say that when + // caches.open(name) returns a promise that succeeds, the value is meant to be a Cache instance. + // However, if the browser was launched with a --user-data-dir param that's "too long" in some sense, + // then even through the promise resolves as success, the value given is `undefined`. + // See https://stackoverflow.com/a/46626574 and https://bugs.chromium.org/p/chromium/issues/detail?id=1054541 + // If we see this happening, return "null" to mean "proceed without caching". + return (await caches.open(cacheName)) || null; + } catch { + // There's no known scenario where we should get an exception here, but considering the + // Chromium bug above, let's tolerate it and treat as "proceed without caching". + return null; + } +} + +function countTotalBytes(loads: LoadLogEntry[]) { + return loads.reduce((prev, item) => prev + (item.responseBytes || 0), 0); +} + +function toDataSizeString(byteCount: number) { + return `${(byteCount / (1024 * 1024)).toFixed(2)} MB`; +} + +function getPerformanceEntry(url: string): PerformanceResourceTiming | undefined { + if (typeof performance !== 'undefined') { + return performance.getEntriesByName(url)[0] as PerformanceResourceTiming; + } +} + +interface LoadLogEntry { + responseBytes: number | undefined; +} + +export interface LoadingResource { + name: string; + url: string; + response: Promise<Response>; +} diff --git a/src/Components/Web.JS/src/Platform/WebAssemblyStartOptions.ts b/src/Components/Web.JS/src/Platform/WebAssemblyStartOptions.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6710e74059f626e5b5dfc234f8b55f5988e3761 --- /dev/null +++ b/src/Components/Web.JS/src/Platform/WebAssemblyStartOptions.ts @@ -0,0 +1,17 @@ +export interface WebAssemblyStartOptions { + /** + * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched + * from a custom source, such as an external CDN. + * @param type The type of the resource to be loaded. + * @param name The name of the resource to be loaded. + * @param defaultUri The URI from which the framework would fetch the resource by default. The URI may be relative or absolute. + * @param integrity The integrity string representing the expected content in the response. + * @returns A URI string or a Response promise to override the loading process, or null/undefined to allow the default loading behavior. + */ + loadBootResource(type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) : string | Promise<Response> | null | undefined; +} + +// This type doesn't have to align with anything in BootConfig. +// Instead, this represents the public API through which certain aspects +// of boot resource loading can be customized. +export type WebAssemblyBootResourceType = 'assembly' | 'pdb' | 'dotnetjs' | 'dotnetwasm' | 'timezonedata'; diff --git a/src/Components/Web.JS/src/Rendering/RenderBatch/OutOfProcessRenderBatch.ts b/src/Components/Web.JS/src/Rendering/RenderBatch/OutOfProcessRenderBatch.ts index e9fc57edf4374317ef9d0ee0208de800b1ac9e84..08d1fdebc033f34ed9bf6a602bce6b87fa5a4ec2 100644 --- a/src/Components/Web.JS/src/Rendering/RenderBatch/OutOfProcessRenderBatch.ts +++ b/src/Components/Web.JS/src/Rendering/RenderBatch/OutOfProcessRenderBatch.ts @@ -1,5 +1,6 @@ import { RenderBatch, ArrayRange, RenderTreeDiff, ArrayValues, RenderTreeEdit, EditType, FrameType, RenderTreeFrame, RenderTreeDiffReader, RenderTreeFrameReader, RenderTreeEditReader, ArrayRangeReader, ArrayBuilderSegmentReader, ArrayBuilderSegment } from './RenderBatch'; -import { decodeUtf8 } from './Utf8Decoder'; +import { decodeUtf8 } from '../../Utf8Decoder'; +import { readInt32LE, readUint64LE, readLEB128, numLEB128Bytes } from '../../BinaryDecoder'; const updatedComponentsEntryLength = 4; // Each is a single int32 giving the location of the data const referenceFramesEntryLength = 20; // 1 int for frame type, then 16 bytes for type-specific data @@ -7,8 +8,6 @@ const disposedComponentIdsEntryLength = 4; // Each is an int32 giving the ID const disposedEventHandlerIdsEntryLength = 8; // Each is an int64 giving the ID const editsEntryLength = 16; // 4 ints const stringTableEntryLength = 4; // Each is an int32 giving the string data location, or -1 for null -const uint64HighPartShift = Math.pow(2, 32); -const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER export class OutOfProcessRenderBatch implements RenderBatch { constructor(private batchData: Uint8Array) { @@ -230,47 +229,4 @@ class OutOfProcessArrayBuilderSegmentReader implements ArrayBuilderSegmentReader } } -function readInt32LE(buffer: Uint8Array, position: number): any { - return (buffer[position]) - | (buffer[position + 1] << 8) - | (buffer[position + 2] << 16) - | (buffer[position + 3] << 24); -} - -function readUint32LE(buffer: Uint8Array, position: number): any { - return (buffer[position]) - + (buffer[position + 1] << 8) - + (buffer[position + 2] << 16) - + ((buffer[position + 3] << 24) >>> 0); // The >>> 0 coerces the value to unsigned -} - -function readUint64LE(buffer: Uint8Array, position: number): any { - // This cannot be done using bit-shift operators in JavaScript, because - // those all implicitly convert to int32 - const highPart = readUint32LE(buffer, position + 4); - if (highPart > maxSafeNumberHighPart) { - throw new Error(`Cannot read uint64 with high order part ${highPart}, because the result would exceed Number.MAX_SAFE_INTEGER.`); - } - - return (highPart * uint64HighPartShift) + readUint32LE(buffer, position); -} - -function readLEB128(buffer: Uint8Array, position: number) { - let result = 0; - let shift = 0; - for (let index = 0; index < 4; index++) { - const byte = buffer[position + index]; - result |= (byte & 127) << shift; - if (byte < 128) { - break; - } - shift += 7; - } - return result; -} -function numLEB128Bytes(value: number) { - return value < 128 ? 1 - : value < 16384 ? 2 - : value < 2097152 ? 3 : 4; -} diff --git a/src/Components/Web.JS/src/Rendering/RenderBatch/SharedMemoryRenderBatch.ts b/src/Components/Web.JS/src/Rendering/RenderBatch/SharedMemoryRenderBatch.ts index 853900594d01e2c3e27270aaceb812cf17582f31..a7fd83214c898c97b860d20017bab1130a953a86 100644 --- a/src/Components/Web.JS/src/Rendering/RenderBatch/SharedMemoryRenderBatch.ts +++ b/src/Components/Web.JS/src/Rendering/RenderBatch/SharedMemoryRenderBatch.ts @@ -1,5 +1,5 @@ import { platform } from '../../Environment'; -import { RenderBatch, ArrayRange, ArrayRangeReader, ArrayBuilderSegment, RenderTreeDiff, RenderTreeEdit, RenderTreeFrame, ArrayValues, EditType, FrameType, RenderTreeFrameReader } from './RenderBatch'; +import { RenderBatch, ArrayRange, ArrayBuilderSegment, RenderTreeDiff, RenderTreeEdit, RenderTreeFrame, ArrayValues, EditType, FrameType, RenderTreeFrameReader } from './RenderBatch'; import { Pointer, System_Array, System_Object } from '../../Platform/Platform'; // Used when running on Mono WebAssembly for shared-memory interop. The code here encapsulates @@ -107,7 +107,7 @@ const frameReader = { textContent: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16), markupContent: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16)!, attributeName: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 16), - attributeValue: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 24), + attributeValue: (frame: RenderTreeFrame) => platform.readStringField(frame as any, 24, true), attributeEventHandlerId: (frame: RenderTreeFrame) => platform.readUint64Field(frame as any, 8), }; diff --git a/src/Components/Web.JS/src/Services/NavigationManager.ts b/src/Components/Web.JS/src/Services/NavigationManager.ts index 5f217d3659302cf807c3fa58a90899ce6b9dd200..61f9532465091c5dd5d6fcaa091929d24f347e3d 100644 --- a/src/Components/Web.JS/src/Services/NavigationManager.ts +++ b/src/Components/Web.JS/src/Services/NavigationManager.ts @@ -13,8 +13,8 @@ export const internalFunctions = { listenForNavigationEvents, enableNavigationInterception, navigateTo, - getBaseURI: () => document.baseURI, - getLocationHref: () => location.href, + getBaseURI: () => BINDING.js_string_to_mono_string(document.baseURI), + getLocationHref: () => BINDING.js_string_to_mono_string(location.href), }; function listenForNavigationEvents(callback: (uri: string, intercepted: boolean) => Promise<void>) { @@ -72,12 +72,12 @@ export function attachToEventDelegator(eventDelegator: EventDelegator) { }); } -export function navigateTo(uri: string, forceLoad: boolean) { +export function navigateTo(uri: string, forceLoad: boolean, replace: boolean = false) { const absoluteUri = toAbsoluteUri(uri); if (!forceLoad && isWithinBaseUriSpace(absoluteUri)) { // It's an internal URL, so do client-side navigation - performInternalNavigation(absoluteUri, false); + performInternalNavigation(absoluteUri, false, replace); } else if (forceLoad && location.href === uri) { // Force-loading the same URL you're already on requires special handling to avoid // triggering browser-specific behavior issues. @@ -85,13 +85,15 @@ export function navigateTo(uri: string, forceLoad: boolean) { const temporaryUri = uri + '?'; history.replaceState(null, '', temporaryUri); location.replace(uri); + } else if (replace){ + history.replaceState(null, '', absoluteUri) } else { // It's either an external URL, or forceLoad is requested, so do a full page load location.href = uri; } } -function performInternalNavigation(absoluteInternalHref: string, interceptedLink: boolean) { +function performInternalNavigation(absoluteInternalHref: string, interceptedLink: boolean, replace: boolean = false) { // Since this was *not* triggered by a back/forward gesture (that goes through a different // code path starting with a popstate event), we don't want to preserve the current scroll // position, so reset it. @@ -99,7 +101,11 @@ function performInternalNavigation(absoluteInternalHref: string, interceptedLink // we render the new page. As a best approximation, wait until the next batch. resetScrollAfterNextBatch(); - history.pushState(null, /* ignored title */ '', absoluteInternalHref); + if(!replace){ + history.pushState(null, /* ignored title */ '', absoluteInternalHref); + }else{ + history.replaceState(null, /* ignored title */ '', absoluteInternalHref); + } notifyLocationChanged(interceptedLink); } @@ -110,7 +116,7 @@ async function notifyLocationChanged(interceptedLink: boolean) { } let testAnchor: HTMLAnchorElement; -function toAbsoluteUri(relativeUri: string) { +export function toAbsoluteUri(relativeUri: string) { testAnchor = testAnchor || document.createElement('a'); testAnchor.href = relativeUri; return testAnchor.href; @@ -135,4 +141,4 @@ function toBaseUriWithTrailingSlash(baseUri: string) { function eventHasSpecialKey(event: MouseEvent) { return event.ctrlKey || event.shiftKey || event.altKey || event.metaKey; -} +} \ No newline at end of file diff --git a/src/Components/Web.JS/src/Rendering/RenderBatch/Utf8Decoder.ts b/src/Components/Web.JS/src/Utf8Decoder.ts similarity index 100% rename from src/Components/Web.JS/src/Rendering/RenderBatch/Utf8Decoder.ts rename to src/Components/Web.JS/src/Utf8Decoder.ts diff --git a/src/Components/Web.JS/yarn.lock b/src/Components/Web.JS/yarn.lock index 6fcffaae8afc3a945bcd3254134dd215d25db18c..1a245c12bddc53ff3c201c1c91d49340d60a0c95 100644 --- a/src/Components/Web.JS/yarn.lock +++ b/src/Components/Web.JS/yarn.lock @@ -10,144 +10,197 @@ version "0.0.0" uid "" -"@babel/code-frame@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" - integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" + integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== dependencies: - "@babel/highlight" "^7.0.0" + "@babel/highlight" "^7.8.3" "@babel/core@^7.1.0": - version "7.3.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.3.tgz#d090d157b7c5060d05a05acaebc048bd2b037947" - integrity sha512-w445QGI2qd0E0GlSnq6huRZWPMmQGCp5gd5ZWS4hagn0EiwzxD5QMFkpchyusAyVC1n27OKXzQ0/88aVU9n4xQ== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.3.3" - "@babel/helpers" "^7.2.0" - "@babel/parser" "^7.3.3" - "@babel/template" "^7.2.2" - "@babel/traverse" "^7.2.2" - "@babel/types" "^7.3.3" - convert-source-map "^1.1.0" + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" + integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.0" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helpers" "^7.9.0" + "@babel/parser" "^7.9.0" + "@babel/template" "^7.8.6" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + convert-source-map "^1.7.0" debug "^4.1.0" - json5 "^2.1.0" - lodash "^4.17.11" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.13" resolve "^1.3.2" semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.0.0", "@babel/generator@^7.2.2", "@babel/generator@^7.3.3": - version "7.3.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.3.tgz#185962ade59a52e00ca2bdfcfd1d58e528d4e39e" - integrity sha512-aEADYwRRZjJyMnKN7llGIlircxTCofm3dtV5pmY6ob18MSIuipHpA2yZWkPlycwu5HJcx/pADS3zssd8eY7/6A== +"@babel/generator@^7.4.0", "@babel/generator@^7.9.0", "@babel/generator@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9" + integrity sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ== dependencies: - "@babel/types" "^7.3.3" + "@babel/types" "^7.9.5" jsesc "^2.5.1" - lodash "^4.17.11" + lodash "^4.17.13" source-map "^0.5.0" - trim-right "^1.0.1" -"@babel/helper-function-name@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" - integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== - dependencies: - "@babel/helper-get-function-arity" "^7.0.0" - "@babel/template" "^7.1.0" - "@babel/types" "^7.0.0" - -"@babel/helper-get-function-arity@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" - integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== - dependencies: - "@babel/types" "^7.0.0" - -"@babel/helper-plugin-utils@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" - integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== - -"@babel/helper-split-export-declaration@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz#3aae285c0311c2ab095d997b8c9a94cad547d813" - integrity sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag== - dependencies: - "@babel/types" "^7.0.0" - -"@babel/helpers@^7.2.0": - version "7.3.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.3.1.tgz#949eec9ea4b45d3210feb7dc1c22db664c9e44b9" - integrity sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA== - dependencies: - "@babel/template" "^7.1.2" - "@babel/traverse" "^7.1.5" - "@babel/types" "^7.3.0" - -"@babel/highlight@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" - integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw== - dependencies: +"@babel/helper-function-name@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c" + integrity sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw== + dependencies: + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/types" "^7.9.5" + +"@babel/helper-get-function-arity@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" + integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-member-expression-to-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" + integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-module-imports@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" + integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-module-transforms@^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" + integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.6" + "@babel/helper-simple-access" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/template" "^7.8.6" + "@babel/types" "^7.9.0" + lodash "^4.17.13" + +"@babel/helper-optimise-call-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" + integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" + integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== + +"@babel/helper-replace-supers@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" + integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/traverse" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/helper-simple-access@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" + integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw== + dependencies: + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-split-export-declaration@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" + integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-validator-identifier@^7.9.0", "@babel/helper-validator-identifier@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" + integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g== + +"@babel/helpers@^7.9.0": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" + integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== + dependencies: + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + +"@babel/highlight@^7.8.3": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" + integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ== + dependencies: + "@babel/helper-validator-identifier" "^7.9.0" chalk "^2.0.0" - esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.2.2", "@babel/parser@^7.2.3", "@babel/parser@^7.3.3": - version "7.3.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.3.tgz#092d450db02bdb6ccb1ca8ffd47d8774a91aef87" - integrity sha512-xsH1CJoln2r74hR+y7cg2B5JCPaTh+Hd+EbBRk9nWGSNspuo6krjhX0Om6RnRQuIvFq8wVXCLKH3kwKDYhanSg== - -"@babel/parser@^7.1.0": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.5.tgz#02f077ac8817d3df4a832ef59de67565e71cca4b" - integrity sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g== +"@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0": + version "7.9.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" + integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== "@babel/plugin-syntax-object-rest-spread@^7.0.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e" - integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - -"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907" - integrity sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.2.2" - "@babel/types" "^7.2.2" - -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.2.2": - version "7.2.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8" - integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.2.2" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.0.0" - "@babel/parser" "^7.2.3" - "@babel/types" "^7.2.2" + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/template@^7.4.0", "@babel/template@^7.8.3", "@babel/template@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" + integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/parser" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2" + integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.5" + "@babel/helper-function-name" "^7.9.5" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/parser" "^7.9.0" + "@babel/types" "^7.9.5" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.10" + lodash "^4.17.13" -"@babel/types@^7.0.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.3.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.3.tgz#6c44d1cdac2a7625b624216657d5bc6c107ab436" - integrity sha512-2tACZ80Wg09UnPg5uGAOUvvInaqLk3l/IAhQzlxLQOIXacr6bMsra5SH6AWw/hIDRCSbCdHP2KzSOD+cT7TzMQ== +"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444" + integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg== dependencies: - esutils "^2.0.2" - lodash "^4.17.11" + "@babel/helper-validator-identifier" "^7.9.5" + lodash "^4.17.13" to-fast-properties "^2.0.0" "@cnakazawa/watch@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef" - integrity sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA== + version "1.0.4" + resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" + integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== dependencies: exec-sh "^0.3.2" minimist "^1.2.0" @@ -156,76 +209,77 @@ version "3.0.0-preview9.19415.3" resolved "https://dotnet.myget.org/F/aspnetcore-dev/npm/@dotnet/jsinterop/-/@dotnet/jsinterop-3.0.0-preview9.19415.3.tgz#f44f482897c612e8d174b8f6d8795d2cda75d643" -"@jest/console@^24.7.1": - version "24.7.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.7.1.tgz#32a9e42535a97aedfe037e725bd67e954b459545" - integrity sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg== +"@jest/console@^24.7.1", "@jest/console@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.9.0.tgz#79b1bc06fb74a8cfb01cbdedf945584b1b9707f0" + integrity sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ== dependencies: - "@jest/source-map" "^24.3.0" + "@jest/source-map" "^24.9.0" chalk "^2.0.1" slash "^2.0.0" -"@jest/core@^24.8.0": - version "24.8.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.8.0.tgz#fbbdcd42a41d0d39cddbc9f520c8bab0c33eed5b" - integrity sha512-R9rhAJwCBQzaRnrRgAdVfnglUuATXdwTRsYqs6NMdVcAl5euG8LtWDe+fVkN27YfKVBW61IojVsXKaOmSnqd/A== +"@jest/core@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.9.0.tgz#2ceccd0b93181f9c4850e74f2a9ad43d351369c4" + integrity sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A== dependencies: "@jest/console" "^24.7.1" - "@jest/reporters" "^24.8.0" - "@jest/test-result" "^24.8.0" - "@jest/transform" "^24.8.0" - "@jest/types" "^24.8.0" + "@jest/reporters" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" ansi-escapes "^3.0.0" chalk "^2.0.1" exit "^0.1.2" graceful-fs "^4.1.15" - jest-changed-files "^24.8.0" - jest-config "^24.8.0" - jest-haste-map "^24.8.0" - jest-message-util "^24.8.0" + jest-changed-files "^24.9.0" + jest-config "^24.9.0" + jest-haste-map "^24.9.0" + jest-message-util "^24.9.0" jest-regex-util "^24.3.0" - jest-resolve-dependencies "^24.8.0" - jest-runner "^24.8.0" - jest-runtime "^24.8.0" - jest-snapshot "^24.8.0" - jest-util "^24.8.0" - jest-validate "^24.8.0" - jest-watcher "^24.8.0" + jest-resolve "^24.9.0" + jest-resolve-dependencies "^24.9.0" + jest-runner "^24.9.0" + jest-runtime "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" + jest-watcher "^24.9.0" micromatch "^3.1.10" p-each-series "^1.0.0" - pirates "^4.0.1" realpath-native "^1.1.0" rimraf "^2.5.4" + slash "^2.0.0" strip-ansi "^5.0.0" -"@jest/environment@^24.8.0": - version "24.8.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.8.0.tgz#0342261383c776bdd652168f68065ef144af0eac" - integrity sha512-vlGt2HLg7qM+vtBrSkjDxk9K0YtRBi7HfRFaDxoRtyi+DyVChzhF20duvpdAnKVBV6W5tym8jm0U9EfXbDk1tw== - dependencies: - "@jest/fake-timers" "^24.8.0" - "@jest/transform" "^24.8.0" - "@jest/types" "^24.8.0" - jest-mock "^24.8.0" - -"@jest/fake-timers@^24.8.0": - version "24.8.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.8.0.tgz#2e5b80a4f78f284bcb4bd5714b8e10dd36a8d3d1" - integrity sha512-2M4d5MufVXwi6VzZhJ9f5S/wU4ud2ck0kxPof1Iz3zWx6Y+V2eJrES9jEktB6O3o/oEyk+il/uNu9PvASjWXQw== - dependencies: - "@jest/types" "^24.8.0" - jest-message-util "^24.8.0" - jest-mock "^24.8.0" - -"@jest/reporters@^24.8.0": - version "24.8.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.8.0.tgz#075169cd029bddec54b8f2c0fc489fd0b9e05729" - integrity sha512-eZ9TyUYpyIIXfYCrw0UHUWUvE35vx5I92HGMgS93Pv7du+GHIzl+/vh8Qj9MCWFK/4TqyttVBPakWMOfZRIfxw== - dependencies: - "@jest/environment" "^24.8.0" - "@jest/test-result" "^24.8.0" - "@jest/transform" "^24.8.0" - "@jest/types" "^24.8.0" +"@jest/environment@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.9.0.tgz#21e3afa2d65c0586cbd6cbefe208bafade44ab18" + integrity sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ== + dependencies: + "@jest/fake-timers" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + +"@jest/fake-timers@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.9.0.tgz#ba3e6bf0eecd09a636049896434d306636540c93" + integrity sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A== + dependencies: + "@jest/types" "^24.9.0" + jest-message-util "^24.9.0" + jest-mock "^24.9.0" + +"@jest/reporters@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.9.0.tgz#86660eff8e2b9661d042a8e98a028b8d631a5b43" + integrity sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw== + dependencies: + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" chalk "^2.0.1" exit "^0.1.2" glob "^7.1.2" @@ -233,83 +287,84 @@ istanbul-lib-instrument "^3.0.1" istanbul-lib-report "^2.0.4" istanbul-lib-source-maps "^3.0.1" - istanbul-reports "^2.1.1" - jest-haste-map "^24.8.0" - jest-resolve "^24.8.0" - jest-runtime "^24.8.0" - jest-util "^24.8.0" + istanbul-reports "^2.2.6" + jest-haste-map "^24.9.0" + jest-resolve "^24.9.0" + jest-runtime "^24.9.0" + jest-util "^24.9.0" jest-worker "^24.6.0" - node-notifier "^5.2.1" + node-notifier "^5.4.2" slash "^2.0.0" source-map "^0.6.0" string-length "^2.0.0" -"@jest/source-map@^24.3.0": - version "24.3.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.3.0.tgz#563be3aa4d224caf65ff77edc95cd1ca4da67f28" - integrity sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag== +"@jest/source-map@^24.3.0", "@jest/source-map@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.9.0.tgz#0e263a94430be4b41da683ccc1e6bffe2a191714" + integrity sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg== dependencies: callsites "^3.0.0" graceful-fs "^4.1.15" source-map "^0.6.0" -"@jest/test-result@^24.8.0": - version "24.8.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.8.0.tgz#7675d0aaf9d2484caa65e048d9b467d160f8e9d3" - integrity sha512-+YdLlxwizlfqkFDh7Mc7ONPQAhA4YylU1s529vVM1rsf67vGZH/2GGm5uO8QzPeVyaVMobCQ7FTxl38QrKRlng== +"@jest/test-result@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.9.0.tgz#11796e8aa9dbf88ea025757b3152595ad06ba0ca" + integrity sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA== dependencies: - "@jest/console" "^24.7.1" - "@jest/types" "^24.8.0" + "@jest/console" "^24.9.0" + "@jest/types" "^24.9.0" "@types/istanbul-lib-coverage" "^2.0.0" -"@jest/test-sequencer@^24.8.0": - version "24.8.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.8.0.tgz#2f993bcf6ef5eb4e65e8233a95a3320248cf994b" - integrity sha512-OzL/2yHyPdCHXEzhoBuq37CE99nkme15eHkAzXRVqthreWZamEMA0WoetwstsQBCXABhczpK03JNbc4L01vvLg== +"@jest/test-sequencer@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz#f8f334f35b625a4f2f355f2fe7e6036dad2e6b31" + integrity sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A== dependencies: - "@jest/test-result" "^24.8.0" - jest-haste-map "^24.8.0" - jest-runner "^24.8.0" - jest-runtime "^24.8.0" + "@jest/test-result" "^24.9.0" + jest-haste-map "^24.9.0" + jest-runner "^24.9.0" + jest-runtime "^24.9.0" -"@jest/transform@^24.8.0": - version "24.8.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.8.0.tgz#628fb99dce4f9d254c6fd9341e3eea262e06fef5" - integrity sha512-xBMfFUP7TortCs0O+Xtez2W7Zu1PLH9bvJgtraN1CDST6LBM/eTOZ9SfwS/lvV8yOfcDpFmwf9bq5cYbXvqsvA== +"@jest/transform@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.9.0.tgz#4ae2768b296553fadab09e9ec119543c90b16c56" + integrity sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^24.8.0" + "@jest/types" "^24.9.0" babel-plugin-istanbul "^5.1.0" chalk "^2.0.1" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" graceful-fs "^4.1.15" - jest-haste-map "^24.8.0" - jest-regex-util "^24.3.0" - jest-util "^24.8.0" + jest-haste-map "^24.9.0" + jest-regex-util "^24.9.0" + jest-util "^24.9.0" micromatch "^3.1.10" + pirates "^4.0.1" realpath-native "^1.1.0" slash "^2.0.0" source-map "^0.6.1" write-file-atomic "2.4.1" -"@jest/types@^24.8.0": - version "24.8.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad" - integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg== +"@jest/types@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59" + integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^12.0.9" + "@types/yargs" "^13.0.0" "@microsoft/signalr@link:../../SignalR/clients/ts/signalr": version "0.0.0" uid "" "@types/babel__core@^7.1.0": - version "7.1.2" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.2.tgz#608c74f55928033fce18b99b213c16be4b3d114f" - integrity sha512-cfCCrFmiGY/yq0NuKNxIQvZFy9kY/1immpSpTngOnyIbD4+eJOG5mxphhHDv3CHL9GltO4GcKr54kGBg3RNdbg== + version "7.1.7" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.7.tgz#1dacad8840364a57c98d0dd4855c6dd3752c6b89" + integrity sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" @@ -318,9 +373,9 @@ "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.0.2.tgz#d2112a6b21fad600d7674274293c85dce0cb47fc" - integrity sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ== + version "7.6.1" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04" + integrity sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew== dependencies: "@babel/types" "^7.0.0" @@ -333,18 +388,21 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.0.7" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.7.tgz#2496e9ff56196cc1429c72034e07eab6121b6f3f" - integrity sha512-CeBpmX1J8kWLcDEnI3Cl2Eo6RfbGvzUctA+CjZUhOKDFbLfcr7fc4usEqLNWetrlJd7RhAkyYe2czXop4fICpw== + version "7.0.10" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.10.tgz#d9a99f017317d9b3d1abc2ced45d3bca68df0daf" + integrity sha512-74fNdUGrWsgIB/V9kTO5FGHPWYY6Eqn+3Z7L6Hc4e/BxjYV7puvBqp5HwsVYYfLm6iURYBNCx4Ut37OF9yitCw== dependencies: "@babel/types" "^7.3.0" -"@types/emscripten@0.0.31": - version "0.0.31" - resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-0.0.31.tgz#160817d1324e8b7049604d39ac47d85eeeedd597" - integrity sha512-6OaHAsknBA6M2gKszMZXunqFofGXCCk4UCXHMdzd3qtBpndSHuM2JgxBE9M3APyl/DlENt4FEe0C7mJwbcC/ZA== - dependencies: - "@types/webassembly-js-api" "*" +"@types/emscripten@^1.39.3": + version "1.39.3" + resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.3.tgz#cac2f24b4739c344c42f928a4c1af1aab245364e" + integrity sha512-F+LPHVwVWp6bkhRq7sMdW0JsQMXd5ZuPbjwU/X6HaAtvIjRmYL2z4BCIopnBRcrzZLorVFf8gxY/GR7szLuPEw== + +"@types/eslint-visitor-keys@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" + integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== "@types/events@*": version "3.0.0" @@ -357,9 +415,9 @@ integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg== "@types/istanbul-lib-report@*": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#e5471e7fa33c61358dd38426189c037a58433b8c" - integrity sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg== + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== dependencies: "@types/istanbul-lib-coverage" "*" @@ -371,17 +429,12 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" -"@types/jest-diff@*": - version "20.0.1" - resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89" - integrity sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA== - -"@types/jest@^24.0.6": - version "24.0.6" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.6.tgz#ba4c8c7900ce098a82ca99293cbe4192bde4f355" - integrity sha512-NE7FBG/F4cMDKdCBqgyd+Sa6JZ5GiMOyA5QwJdeS4Ii/Z9a18WgGbFrHbcr48/7I9HdnkaAYP+S2MmQ27qoqJA== +"@types/jest@^24.9.1": + version "24.9.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.9.1.tgz#02baf9573c78f1b9974a5f36778b366aa77bd534" + integrity sha512-Fb38HkXSVA4L8fGKEZ6le5bB8r6MRWlOCZbVuWZcmOMSCd2wCYOwN1ibj8daIoV9naq7aaOZjrLCoCMptKU/4Q== dependencies: - "@types/jest-diff" "*" + jest-diff "^24.3.0" "@types/jsdom@11.0.6": version "11.0.6" @@ -393,10 +446,15 @@ "@types/tough-cookie" "*" parse5 "^4.0.0" +"@types/json-schema@^7.0.3": + version "7.0.4" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" + integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== + "@types/node@*": - version "11.9.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-11.9.5.tgz#011eece9d3f839a806b63973e228f85967b79ed3" - integrity sha512-vVjM0SVzgaOUpflq4GYBvCpozes8OgIIS5gVXVka+OfK3hvnkC1i93U8WiY2OtNE4XUWyyy/86Kf6e0IHTQw1Q== + version "13.11.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7" + integrity sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g== "@types/stack-utils@^1.0.1": version "1.0.1" @@ -404,191 +462,203 @@ integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== "@types/tough-cookie@*": - version "2.3.5" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.5.tgz#9da44ed75571999b65c37b60c9b2b88db54c585d" - integrity sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg== + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" + integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A== -"@types/webassembly-js-api@*": - version "0.0.2" - resolved "https://registry.yarnpkg.com/@types/webassembly-js-api/-/webassembly-js-api-0.0.2.tgz#43a04bd75fa20332133c6c3986156bfeb4a3ced7" - integrity sha512-htlxJRag6RUiMYUkS8Fjup+TMHO0VarpiF9MrqYaGJ0wXtIraQFz40rfA8VIeCiWy8sgpv3RLmigpgicG8fqGA== +"@types/yargs-parser@*": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" + integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== -"@types/yargs@^12.0.2", "@types/yargs@^12.0.9": - version "12.0.12" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916" - integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw== +"@types/yargs@^13.0.0": + version "13.0.8" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.8.tgz#a38c22def2f1c2068f8971acb3ea734eb3c64a99" + integrity sha512-XAvHLwG7UQ+8M4caKIH0ZozIOYay5fQkAgyIXegXT9jPtdIGdhga+sUEdAr1CiG46aB+c64xQEYyEzlwWVTNzA== + dependencies: + "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.5.0.tgz#85c509bcfc2eb35f37958fa677379c80b7a8f66f" - integrity sha512-TZ5HRDFz6CswqBUviPX8EfS+iOoGbclYroZKT3GWGYiGScX0qo6QjHc5uuM7JN920voP2zgCkHgF5SDEVlCtjQ== +"@typescript-eslint/eslint-plugin@^1.13.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.13.0.tgz#22fed9b16ddfeb402fd7bcde56307820f6ebc49f" + integrity sha512-WQHCozMnuNADiqMtsNzp96FNox5sOVpU8Xt4meaT4em8lOG1SrOv92/mUbEHQVh90sldKSfcOc/I0FOb/14G1g== dependencies: - "@typescript-eslint/parser" "1.5.0" - "@typescript-eslint/typescript-estree" "1.5.0" - requireindex "^1.2.0" + "@typescript-eslint/experimental-utils" "1.13.0" + eslint-utils "^1.3.1" + functional-red-black-tree "^1.0.1" + regexpp "^2.0.1" tsutils "^3.7.0" -"@typescript-eslint/parser@1.5.0", "@typescript-eslint/parser@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-1.5.0.tgz#a96114d195dff2a49534e4c4850fb676f905a072" - integrity sha512-pRWTnJrnxuT0ragdY26hZL+bxqDd4liMlftpH2CBlMPryOIOb1J+MdZuw6R4tIu6bWVdwbHKPTs+Q34LuGvfGw== +"@typescript-eslint/experimental-utils@1.13.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz#b08c60d780c0067de2fb44b04b432f540138301e" + integrity sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg== dependencies: - "@typescript-eslint/typescript-estree" "1.5.0" + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "1.13.0" eslint-scope "^4.0.0" + +"@typescript-eslint/parser@^1.13.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-1.13.0.tgz#61ac7811ea52791c47dc9fd4dd4a184fae9ac355" + integrity sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ== + dependencies: + "@types/eslint-visitor-keys" "^1.0.0" + "@typescript-eslint/experimental-utils" "1.13.0" + "@typescript-eslint/typescript-estree" "1.13.0" eslint-visitor-keys "^1.0.0" -"@typescript-eslint/typescript-estree@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.5.0.tgz#986b356ecdf5a0c3bc9889d221802149cf5dbd4e" - integrity sha512-XqR14d4BcYgxcrpxIwcee7UEjncl9emKc/MgkeUfIk2u85KlsGYyaxC7Zxjmb17JtWERk/NaO+KnBsqgpIXzwA== +"@typescript-eslint/typescript-estree@1.13.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz#8140f17d0f60c03619798f1d628b8434913dc32e" + integrity sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw== dependencies: lodash.unescape "4.0.1" semver "5.5.0" -"@webassemblyjs/ast@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" - integrity sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ== - dependencies: - "@webassemblyjs/helper-module-context" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/wast-parser" "1.8.5" - -"@webassemblyjs/floating-point-hex-parser@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz#1ba926a2923613edce496fd5b02e8ce8a5f49721" - integrity sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ== - -"@webassemblyjs/helper-api-error@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz#c49dad22f645227c5edb610bdb9697f1aab721f7" - integrity sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA== - -"@webassemblyjs/helper-buffer@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz#fea93e429863dd5e4338555f42292385a653f204" - integrity sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q== - -"@webassemblyjs/helper-code-frame@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz#9a740ff48e3faa3022b1dff54423df9aa293c25e" - integrity sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ== - dependencies: - "@webassemblyjs/wast-printer" "1.8.5" - -"@webassemblyjs/helper-fsm@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz#ba0b7d3b3f7e4733da6059c9332275d860702452" - integrity sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow== - -"@webassemblyjs/helper-module-context@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz#def4b9927b0101dc8cbbd8d1edb5b7b9c82eb245" - integrity sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g== - dependencies: - "@webassemblyjs/ast" "1.8.5" - mamacro "^0.0.3" - -"@webassemblyjs/helper-wasm-bytecode@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz#537a750eddf5c1e932f3744206551c91c1b93e61" - integrity sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ== - -"@webassemblyjs/helper-wasm-section@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz#74ca6a6bcbe19e50a3b6b462847e69503e6bfcbf" - integrity sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-buffer" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/wasm-gen" "1.8.5" - -"@webassemblyjs/ieee754@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz#712329dbef240f36bf57bd2f7b8fb9bf4154421e" - integrity sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g== +"@webassemblyjs/ast@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" + integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== + dependencies: + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + +"@webassemblyjs/floating-point-hex-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" + integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== + +"@webassemblyjs/helper-api-error@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" + integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== + +"@webassemblyjs/helper-buffer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" + integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== + +"@webassemblyjs/helper-code-frame@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" + integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== + dependencies: + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/helper-fsm@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" + integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== + +"@webassemblyjs/helper-module-context@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" + integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== + dependencies: + "@webassemblyjs/ast" "1.9.0" + +"@webassemblyjs/helper-wasm-bytecode@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" + integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== + +"@webassemblyjs/helper-wasm-section@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" + integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + +"@webassemblyjs/ieee754@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" + integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.8.5.tgz#044edeb34ea679f3e04cd4fd9824d5e35767ae10" - integrity sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A== +"@webassemblyjs/leb128@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" + integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.8.5.tgz#a8bf3b5d8ffe986c7c1e373ccbdc2a0915f0cedc" - integrity sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw== - -"@webassemblyjs/wasm-edit@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz#962da12aa5acc1c131c81c4232991c82ce56e01a" - integrity sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-buffer" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/helper-wasm-section" "1.8.5" - "@webassemblyjs/wasm-gen" "1.8.5" - "@webassemblyjs/wasm-opt" "1.8.5" - "@webassemblyjs/wasm-parser" "1.8.5" - "@webassemblyjs/wast-printer" "1.8.5" - -"@webassemblyjs/wasm-gen@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz#54840766c2c1002eb64ed1abe720aded714f98bc" - integrity sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/ieee754" "1.8.5" - "@webassemblyjs/leb128" "1.8.5" - "@webassemblyjs/utf8" "1.8.5" - -"@webassemblyjs/wasm-opt@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz#b24d9f6ba50394af1349f510afa8ffcb8a63d264" - integrity sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-buffer" "1.8.5" - "@webassemblyjs/wasm-gen" "1.8.5" - "@webassemblyjs/wasm-parser" "1.8.5" - -"@webassemblyjs/wasm-parser@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz#21576f0ec88b91427357b8536383668ef7c66b8d" - integrity sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-api-error" "1.8.5" - "@webassemblyjs/helper-wasm-bytecode" "1.8.5" - "@webassemblyjs/ieee754" "1.8.5" - "@webassemblyjs/leb128" "1.8.5" - "@webassemblyjs/utf8" "1.8.5" - -"@webassemblyjs/wast-parser@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz#e10eecd542d0e7bd394f6827c49f3df6d4eefb8c" - integrity sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/floating-point-hex-parser" "1.8.5" - "@webassemblyjs/helper-api-error" "1.8.5" - "@webassemblyjs/helper-code-frame" "1.8.5" - "@webassemblyjs/helper-fsm" "1.8.5" +"@webassemblyjs/utf8@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" + integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== + +"@webassemblyjs/wasm-edit@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" + integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/helper-wasm-section" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-opt" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/wasm-gen@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" + integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wasm-opt@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" + integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + +"@webassemblyjs/wasm-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" + integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wast-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" + integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/floating-point-hex-parser" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-code-frame" "1.9.0" + "@webassemblyjs/helper-fsm" "1.9.0" "@xtuc/long" "4.2.2" -"@webassemblyjs/wast-printer@1.8.5": - version "1.8.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz#114bbc481fd10ca0e23b3560fa812748b0bae5bc" - integrity sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg== +"@webassemblyjs/wast-printer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" + integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/wast-parser" "1.8.5" + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" "@xtuc/ieee754@^1.2.0": @@ -602,79 +672,54 @@ integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== abab@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f" - integrity sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w== - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + version "2.0.3" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" + integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== acorn-globals@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.0.tgz#e3b6f8da3c1552a95ae627571f7dd6923bb54103" - integrity sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw== + version "4.3.4" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" + integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== dependencies: acorn "^6.0.1" acorn-walk "^6.0.1" acorn-jsx@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e" - integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg== + version "5.2.0" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" + integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== acorn-walk@^6.0.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.1.1.tgz#d363b66f5fac5f018ff9c3a1e7b6f8e310cc3913" - integrity sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw== + version "6.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" + integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== acorn@^5.5.3: - version "5.7.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" - integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== -acorn@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.0.tgz#b0a3be31752c97a0f7013c5f4903b71a05db6818" - integrity sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw== - -acorn@^6.0.7: - version "6.1.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" - integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== - -acorn@^6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.1.tgz#3ed8422d6dec09e6121cc7a843ca86a330a86b51" - integrity sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q== +acorn@^6.0.1, acorn@^6.0.7, acorn@^6.2.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" + integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== -ajv-keywords@^3.1.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.0.tgz#4b831e7b531415a7cc518cd404e73f6193c6349d" - integrity sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw== - -ajv@^6.1.0, ajv@^6.5.5: - version "6.9.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.2.tgz#4927adb83e7f48e5a32b45729744c71ec39c9c7b" - integrity sha512-4UFy0/LgDo7Oa/+wOAlj44tp9K78u38E5/359eSrqEp1Z5PdVfimCcs7SluXMP755RUQu6d2b4AvF0R1C9RZjg== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" +ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" + integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== -ajv@^6.9.1: - version "6.10.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" - integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== +ajv@^6.1.0, ajv@^6.10.2, ajv@^6.5.5, ajv@^6.9.1: + version "6.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" + integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== dependencies: - fast-deep-equal "^2.0.1" + fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.4.1" uri-js "^4.2.2" @@ -684,22 +729,12 @@ ansi-escapes@^3.0.0, ansi-escapes@^3.2.0: resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - ansi-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= -ansi-regex@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9" - integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w== - -ansi-regex@^4.1.0: +ansi-regex@^4.0.0, ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== @@ -719,19 +754,11 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -aproba@^1.0.3, aproba@^1.1.1: +aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -764,11 +791,6 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= - asn1.js@^4.0.0: version "4.10.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" @@ -791,10 +813,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= assert@^1.1.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" - integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE= + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== dependencies: + object-assign "^4.1.1" util "0.10.3" assign-symbols@^1.0.0: @@ -808,21 +831,21 @@ astral-regex@^1.0.0: integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== async-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" - integrity sha1-GdOGodntxufByF04iu28xW0zYC0= + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== async-limiter@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" - integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -atob@^2.1.1: +atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== @@ -833,46 +856,47 @@ aws-sign2@~0.7.0: integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= aws4@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" - integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + version "1.9.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" + integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== -babel-jest@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.8.0.tgz#5c15ff2b28e20b0f45df43fe6b7f2aae93dba589" - integrity sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw== +babel-jest@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54" + integrity sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw== dependencies: - "@jest/transform" "^24.8.0" - "@jest/types" "^24.8.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" "@types/babel__core" "^7.1.0" babel-plugin-istanbul "^5.1.0" - babel-preset-jest "^24.6.0" + babel-preset-jest "^24.9.0" chalk "^2.4.2" slash "^2.0.0" babel-plugin-istanbul@^5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.1.tgz#7981590f1956d75d67630ba46f0c22493588c893" - integrity sha512-RNNVv2lsHAXJQsEJ5jonQwrJVWK8AcZpG1oxhnjCUaAjL7xahYLANhPUZbzEQHjKy1NMYUwn+0NPKQc8iSY4xQ== + version "5.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz#df4ade83d897a92df069c4d9a25cf2671293c854" + integrity sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw== dependencies: + "@babel/helper-plugin-utils" "^7.0.0" find-up "^3.0.0" - istanbul-lib-instrument "^3.0.0" - test-exclude "^5.0.0" + istanbul-lib-instrument "^3.3.0" + test-exclude "^5.2.3" -babel-plugin-jest-hoist@^24.6.0: - version "24.6.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz#f7f7f7ad150ee96d7a5e8e2c5da8319579e78019" - integrity sha512-3pKNH6hMt9SbOv0F3WVmy5CWQ4uogS3k0GY5XLyQHJ9EGpAT9XWkFd2ZiXXtkwFHdAHa5j7w7kfxSP5lAIwu7w== +babel-plugin-jest-hoist@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz#4f837091eb407e01447c8843cbec546d0002d756" + integrity sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw== dependencies: "@types/babel__traverse" "^7.0.6" -babel-preset-jest@^24.6.0: - version "24.6.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz#66f06136eefce87797539c0d63f1769cc3915984" - integrity sha512-pdZqLEdmy1ZK5kyRUfvBb2IfTPb2BUvIJczlPspS8fWmBQslNNDBqVfh7BW5leOVJMDZKzjD8XEyABTk6gQ5yw== +babel-preset-jest@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz#192b521e2217fb1d1f67cf73f70c336650ad3cdc" + integrity sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg== dependencies: "@babel/plugin-syntax-object-rest-spread" "^7.0.0" - babel-plugin-jest-hoist "^24.6.0" + babel-plugin-jest-hoist "^24.9.0" balanced-match@^1.0.0: version "1.0.0" @@ -880,9 +904,9 @@ balanced-match@^1.0.0: integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= base64-js@^1.0.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" - integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== base@^0.11.1: version "0.11.2" @@ -910,9 +934,16 @@ big.js@^5.2.2: integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== binary-extensions@^1.0.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.0.tgz#9523e001306a32444b907423f1de2164222f6ab1" - integrity sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw== + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" bl@^2.0.1: version "2.2.0" @@ -922,10 +953,10 @@ bl@^2.0.1: readable-stream "^2.3.5" safe-buffer "^5.1.1" -bluebird@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" - integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== +bluebird@^3.5.5: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" @@ -961,10 +992,10 @@ brorand@^1.0.1: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= -browser-process-hrtime@^0.1.2: - version "0.1.3" - resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz#616f00faef1df7ec1b5bf9cfe2bdc3170f26c7b4" - integrity sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw== +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== browser-resolve@^1.11.3: version "1.11.3" @@ -1039,10 +1070,10 @@ bs-logger@0.x: dependencies: fast-json-stable-stringify "2.x" -bser@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" - integrity sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk= +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== dependencies: node-int64 "^0.4.0" @@ -1057,9 +1088,9 @@ buffer-xor@^1.0.3: integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= buffer@^4.3.0: - version "4.9.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" - integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" @@ -1070,22 +1101,23 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -cacache@^11.0.2: - version "11.3.2" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.2.tgz#2d81e308e3d258ca38125b676b98b2ac9ce69bfa" - integrity sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg== +cacache@^12.0.2: + version "12.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" + integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== dependencies: - bluebird "^3.5.3" + bluebird "^3.5.5" chownr "^1.1.1" figgy-pudding "^3.5.1" - glob "^7.1.3" + glob "^7.1.4" graceful-fs "^4.1.15" + infer-owner "^1.0.3" lru-cache "^5.1.1" mississippi "^3.0.0" mkdirp "^0.5.1" move-concurrently "^1.0.1" promise-inflight "^1.0.1" - rimraf "^2.6.2" + rimraf "^2.6.3" ssri "^6.0.1" unique-filename "^1.1.1" y18n "^4.0.0" @@ -1106,19 +1138,19 @@ cache-base@^1.0.1: unset-value "^1.0.0" callsites@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" - integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= -camelcase@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42" - integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA== +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== capture-exit@^2.0.0: version "2.0.0" @@ -1146,10 +1178,10 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -chokidar@^2.0.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.2.tgz#9c23ea40b01638439e0513864d362aeacc5ad058" - integrity sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg== +chokidar@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== dependencies: anymatch "^2.0.0" async-each "^1.0.1" @@ -1161,19 +1193,19 @@ chokidar@^2.0.2: normalize-path "^3.0.0" path-is-absolute "^1.0.0" readdirp "^2.2.1" - upath "^1.1.0" + upath "^1.1.1" optionalDependencies: fsevents "^1.2.7" chownr@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" - integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== -chrome-trace-event@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz#45a91bd2c20c9411f0963b5aaeb9a1b95e09cc48" - integrity sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A== +chrome-trace-event@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" + integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== dependencies: tslib "^1.9.0" @@ -1208,18 +1240,9 @@ cli-cursor@^2.1.0: restore-cursor "^2.0.0" cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= - -cliui@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" - integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== - dependencies: - string-width "^2.1.1" - strip-ansi "^4.0.0" - wrap-ansi "^2.0.0" + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" + integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== cliui@^5.0.0: version "5.0.0" @@ -1235,11 +1258,6 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -1261,16 +1279,16 @@ color-name@1.1.3: integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" - integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" -commander@~2.17.1: - version "2.17.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" - integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== commondir@^1.0.1: version "1.0.1" @@ -1278,9 +1296,9 @@ commondir@^1.0.1: integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= component-emitter@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" - integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== concat-map@0.0.1: version "0.0.1" @@ -1298,26 +1316,19 @@ concat-stream@^1.5.0: typedarray "^0.0.6" console-browserify@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" - integrity sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA= - dependencies: - date-now "^0.1.4" - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + version "1.2.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -convert-source-map@^1.1.0, convert-source-map@^1.4.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" - integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== +convert-source-map@^1.4.0, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== dependencies: safe-buffer "~5.1.1" @@ -1403,21 +1414,21 @@ crypto-browserify@^3.11.0: randomfill "^1.0.3" cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": - version "0.3.6" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.6.tgz#f85206cee04efa841f3c5982a74ba96ab20d65ad" - integrity sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A== + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== cssstyle@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.2.1.tgz#3aceb2759eaf514ac1a21628d723d6043a819495" - integrity sha512-7DYm8qe+gPx/h77QlCyFmX80+fGaE/6A/Ekl0zaszYOubvySO2saYFdQ78P29D0UsULxFKCetDGNaNRUdSF+2A== + version "1.4.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" + integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== dependencies: cssom "0.3.x" -cyclist@~0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" - integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= +cyclist@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" + integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= dashdash@^1.12.0: version "1.14.1" @@ -1435,12 +1446,7 @@ data-urls@^1.0.0: whatwg-mimetype "^2.2.0" whatwg-url "^7.0.0" -date-now@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" - integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= - -debug@^2.1.2, debug@^2.2.0, debug@^2.3.3: +debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -1464,17 +1470,12 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -define-properties@^1.1.2: +define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -1508,15 +1509,10 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - des.js@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" - integrity sha1-wHTS4qpqipoH29YfmhXCzYPsjsw= + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== dependencies: inherits "^2.0.1" minimalistic-assert "^1.0.0" @@ -1526,20 +1522,15 @@ detect-file@^1.0.0: resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - detect-newline@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= -diff-sequences@^24.3.0: - version "24.3.0" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975" - integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw== +diff-sequences@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" + integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== diffie-hellman@^5.0.0: version "5.0.3" @@ -1588,9 +1579,9 @@ ecc-jsbn@~0.1.1: safer-buffer "^2.1.0" elliptic@^6.0.0: - version "6.4.1" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a" - integrity sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ== + version "6.5.2" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" + integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== dependencies: bn.js "^4.4.0" brorand "^1.0.1" @@ -1610,14 +1601,19 @@ emojis-list@^2.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" - integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" -enhanced-resolve@4.1.0, enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: +enhanced-resolve@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== @@ -1626,6 +1622,15 @@ enhanced-resolve@4.1.0, enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: memory-fs "^0.4.0" tapable "^1.0.0" +enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66" + integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + errno@^0.1.3, errno@~0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" @@ -1640,22 +1645,27 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.5.1: - version "1.13.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" - integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== +es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: + version "1.17.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" + integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== dependencies: - es-to-primitive "^1.2.0" + es-to-primitive "^1.2.1" function-bind "^1.1.1" has "^1.0.3" - is-callable "^1.1.4" - is-regex "^1.0.4" - object-keys "^1.0.12" - -es-to-primitive@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" - integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== dependencies: is-callable "^1.1.4" is-date-object "^1.0.1" @@ -1667,26 +1677,18 @@ escape-string-regexp@^1.0.5: integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= escodegen@^1.9.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.1.tgz#c485ff8d6b4cdb89e27f4a856e91f118401ca510" - integrity sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw== + version "1.14.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457" + integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ== dependencies: - esprima "^3.1.3" + esprima "^4.0.1" estraverse "^4.2.0" esutils "^2.0.2" optionator "^0.8.1" optionalDependencies: source-map "~0.6.1" -eslint-scope@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" - integrity sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - -eslint-scope@^4.0.3: +eslint-scope@^4.0.0, eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== @@ -1695,14 +1697,16 @@ eslint-scope@^4.0.3: estraverse "^4.1.1" eslint-utils@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512" - integrity sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q== + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" -eslint-visitor-keys@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" - integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" + integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== eslint@^5.16.0: version "5.16.0" @@ -1755,22 +1759,17 @@ espree@^5.0.1: acorn-jsx "^5.0.0" eslint-visitor-keys "^1.0.0" -esprima@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" - integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= - -esprima@^4.0.0: +esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" - integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== + version "1.3.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.0.tgz#e5e29a6f66a837840d34f68cb9ce355260d1128b" + integrity sha512-/5qB+Mb0m2bh86tjGbA8pB0qBfdmCIK6ZNPjcw4/TtEH0+tTf0wLA5HK4KMTweSMwLGHwBDWCBV+6+2+EuHmgg== dependencies: - estraverse "^4.0.0" + estraverse "^5.0.0" esrecurse@^4.1.0: version "4.2.1" @@ -1779,20 +1778,25 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" -estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" - integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= +estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" + integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== esutils@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" - integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== events@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" - integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA== + version "3.1.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" + integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== eventsource@^1.0.7: version "1.0.7" @@ -1810,9 +1814,9 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: safe-buffer "^5.1.1" exec-sh@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.2.tgz#6738de2eb7c8e671d0366aea0b0db8c6f7d7391b" - integrity sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg== + version "0.3.4" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" + integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== execa@^1.0.0: version "1.0.0" @@ -1852,17 +1856,17 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -expect@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-24.8.0.tgz#471f8ec256b7b6129ca2524b2a62f030df38718d" - integrity sha512-/zYvP8iMDrzaaxHVa724eJBCKqSHmO0FA7EDkBiRHxg6OipmMn1fN+C8T9L9K8yr7UONkOifu6+LLH+z76CnaA== +expect@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca" + integrity sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q== dependencies: - "@jest/types" "^24.8.0" + "@jest/types" "^24.9.0" ansi-styles "^3.2.0" - jest-get-type "^24.8.0" - jest-matcher-utils "^24.8.0" - jest-message-util "^24.8.0" - jest-regex-util "^24.3.0" + jest-get-type "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-regex-util "^24.9.0" extend-shallow@^2.0.1: version "2.0.1" @@ -1885,9 +1889,9 @@ extend@~3.0.2: integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== external-editor@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27" - integrity sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA== + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== dependencies: chardet "^0.7.0" iconv-lite "^0.4.24" @@ -1917,32 +1921,32 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@~2.0.4: +fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= fb-watchman@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" - integrity sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg= + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== dependencies: - bser "^2.0.0" + bser "2.1.1" figgy-pudding@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" - integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== + version "3.5.2" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" + integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== figures@^2.0.0: version "2.0.0" @@ -1958,6 +1962,11 @@ file-entry-cache@^5.0.1: dependencies: flat-cache "^2.0.1" +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" @@ -1968,13 +1977,13 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" -find-cache-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d" - integrity sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA== +find-cache-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== dependencies: commondir "^1.0.1" - make-dir "^1.0.0" + make-dir "^2.0.0" pkg-dir "^3.0.0" find-up@^3.0.0: @@ -2004,9 +2013,9 @@ flat-cache@^2.0.1: write "1.0.3" flatted@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" - integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== flush-write-stream@^1.0.0: version "1.1.1" @@ -2050,13 +2059,6 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-minipass@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" - integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ== - dependencies: - minipass "^2.2.1" - fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" @@ -2073,12 +2075,12 @@ fs.realpath@^1.0.0: integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= fsevents@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4" - integrity sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw== + version "1.2.12" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.12.tgz#db7e0d8ec3b0b45724fd4d83d43554a8f1f0de5c" + integrity sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q== dependencies: - nan "^2.9.2" - node-pre-gyp "^0.10.0" + bindings "^1.5.0" + nan "^2.12.1" function-bind@^1.1.1: version "1.1.1" @@ -2090,24 +2092,10 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== get-caller-file@^2.0.1: version "2.0.5" @@ -2141,22 +2129,10 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob@^7.1.1, glob@^7.1.2: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.1.3: - version "7.1.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" - integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -2202,37 +2178,26 @@ global-prefix@^3.0.0: which "^1.3.1" globals@^11.1.0, globals@^11.7.0: - version "11.11.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.11.0.tgz#dcf93757fa2de5486fbeed7118538adf789e9c2e" - integrity sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw== + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: - version "4.1.15" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" - integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= -handlebars@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" - integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw== - dependencies: - neo-async "^2.6.0" - optimist "^0.6.1" - source-map "^0.6.1" - optionalDependencies: - uglify-js "^3.1.4" - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.1.0: +har-validator@~5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== @@ -2245,15 +2210,10 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= -has-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" - integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== has-value@^0.3.1: version "0.3.1" @@ -2286,7 +2246,7 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.1, has@^1.0.3: +has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== @@ -2326,9 +2286,9 @@ homedir-polyfill@^1.0.1: parse-passwd "^1.0.0" hosted-git-info@^2.1.4: - version "2.7.1" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" - integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== html-encoding-sniffer@^1.0.2: version "1.0.2" @@ -2337,6 +2297,11 @@ html-encoding-sniffer@^1.0.2: dependencies: whatwg-encoding "^1.0.1" +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -2351,7 +2316,7 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: +iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -2359,31 +2324,24 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: safer-buffer ">= 2.1.2 < 3" ieee754@^1.1.4: - version "1.1.12" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" - integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= -ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== - dependencies: - minimatch "^3.0.4" - ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== import-fresh@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz#a3d897f420cab0e671236897f75bc14b4885c390" - integrity sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ== + version "3.2.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" + integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" @@ -2401,10 +2359,10 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= -indexof@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" - integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= +infer-owner@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== inflight@^1.0.4: version "1.0.6" @@ -2429,15 +2387,15 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: +ini@^1.3.4, ini@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== inquirer@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.2.tgz#46941176f65c9eb20804627149b743a218f25406" - integrity sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA== + version "6.5.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" + integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== dependencies: ansi-escapes "^3.2.0" chalk "^2.4.2" @@ -2445,12 +2403,12 @@ inquirer@^6.2.2: cli-width "^2.0.0" external-editor "^3.0.3" figures "^2.0.0" - lodash "^4.17.11" + lodash "^4.17.12" mute-stream "0.0.7" run-async "^2.2.0" rxjs "^6.4.0" string-width "^2.1.0" - strip-ansi "^5.0.0" + strip-ansi "^5.1.0" through "^2.3.6" interpret@1.2.0: @@ -2501,10 +2459,10 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" - integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== +is-callable@^1.1.4, is-callable@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" + integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== is-ci@^2.0.0: version "2.0.0" @@ -2528,9 +2486,9 @@ is-data-descriptor@^1.0.0: kind-of "^6.0.0" is-date-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" - integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== is-descriptor@^0.1.0: version "0.1.6" @@ -2567,22 +2525,15 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= is-generator-fn@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.0.0.tgz#038c31b774709641bda678b1f06a4e3227c10b3e" - integrity sha512-elzyIdM7iKoFHzcrndIqjYomImhxrFRnGP3galODoII4TB9gI7mZ+FnlLQmmjf27SxHS2gKEeyhX5/+YRS6H9g== + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== is-glob@^3.1.0: version "3.1.0" @@ -2592,9 +2543,9 @@ is-glob@^3.1.0: is-extglob "^2.1.0" is-glob@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" - integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== dependencies: is-extglob "^2.1.1" @@ -2617,12 +2568,12 @@ is-promise@^2.1.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= -is-regex@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" - integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= +is-regex@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" + integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== dependencies: - has "^1.0.1" + has "^1.0.3" is-stream@^1.1.0: version "1.1.0" @@ -2630,11 +2581,11 @@ is-stream@^1.1.0: integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= is-symbol@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" - integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== dependencies: - has-symbols "^1.0.0" + has-symbols "^1.0.1" is-typedarray@~1.0.0: version "1.0.0" @@ -2678,354 +2629,356 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#0b891e5ad42312c2b9488554f603795f9a2211ba" - integrity sha512-dKWuzRGCs4G+67VfW9pBFFz2Jpi4vSp/k7zBcJ888ofV5Mi1g5CUML5GvMvV6u9Cjybftu+E8Cgp+k0dI1E5lw== - -istanbul-lib-instrument@^3.0.0, istanbul-lib-instrument@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.1.0.tgz#a2b5484a7d445f1f311e93190813fa56dfb62971" - integrity sha512-ooVllVGT38HIk8MxDj/OIHXSYvH+1tq/Vb38s8ixt9GoJadXska4WkGY+0wkmtYCZNYtaARniH/DixUGGLZ0uA== - dependencies: - "@babel/generator" "^7.0.0" - "@babel/parser" "^7.0.0" - "@babel/template" "^7.0.0" - "@babel/traverse" "^7.0.0" - "@babel/types" "^7.0.0" - istanbul-lib-coverage "^2.0.3" - semver "^5.5.0" +istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" + integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== + +istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" + integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== + dependencies: + "@babel/generator" "^7.4.0" + "@babel/parser" "^7.4.3" + "@babel/template" "^7.4.0" + "@babel/traverse" "^7.4.3" + "@babel/types" "^7.4.0" + istanbul-lib-coverage "^2.0.5" + semver "^6.0.0" istanbul-lib-report@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.4.tgz#bfd324ee0c04f59119cb4f07dab157d09f24d7e4" - integrity sha512-sOiLZLAWpA0+3b5w5/dq0cjm2rrNdAfHWaGhmn7XEFW6X++IV9Ohn+pnELAl9K3rfpaeBfbmH9JU5sejacdLeA== + version "2.0.8" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33" + integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== dependencies: - istanbul-lib-coverage "^2.0.3" - make-dir "^1.3.0" - supports-color "^6.0.0" + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + supports-color "^6.1.0" istanbul-lib-source-maps@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.2.tgz#f1e817229a9146e8424a28e5d69ba220fda34156" - integrity sha512-JX4v0CiKTGp9fZPmoxpu9YEkPbEqCqBbO3403VabKjH+NRXo72HafD5UgnjTEqHL2SAjaZK1XDuDOkn6I5QVfQ== + version "3.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" + integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== dependencies: debug "^4.1.1" - istanbul-lib-coverage "^2.0.3" - make-dir "^1.3.0" - rimraf "^2.6.2" + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + rimraf "^2.6.3" source-map "^0.6.1" -istanbul-reports@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.1.1.tgz#72ef16b4ecb9a4a7bd0e2001e00f95d1eec8afa9" - integrity sha512-FzNahnidyEPBCI0HcufJoSEoKykesRlFcSzQqjH9x0+LC8tnnE/p/90PBLu8iZTxr8yYZNyTtiAujUqyN+CIxw== +istanbul-reports@^2.2.6: + version "2.2.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.7.tgz#5d939f6237d7b48393cc0959eab40cd4fd056931" + integrity sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg== dependencies: - handlebars "^4.1.0" + html-escaper "^2.0.0" -jest-changed-files@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.8.0.tgz#7e7eb21cf687587a85e50f3d249d1327e15b157b" - integrity sha512-qgANC1Yrivsq+UrLXsvJefBKVoCsKB0Hv+mBb6NMjjZ90wwxCDmU3hsCXBya30cH+LnPYjwgcU65i6yJ5Nfuug== +jest-changed-files@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039" + integrity sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg== dependencies: - "@jest/types" "^24.8.0" + "@jest/types" "^24.9.0" execa "^1.0.0" throat "^4.0.0" -jest-cli@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.8.0.tgz#b075ac914492ed114fa338ade7362a301693e989" - integrity sha512-+p6J00jSMPQ116ZLlHJJvdf8wbjNbZdeSX9ptfHX06/MSNaXmKihQzx5vQcw0q2G6JsdVkUIdWbOWtSnaYs3yA== +jest-cli@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.9.0.tgz#ad2de62d07472d419c6abc301fc432b98b10d2af" + integrity sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg== dependencies: - "@jest/core" "^24.8.0" - "@jest/test-result" "^24.8.0" - "@jest/types" "^24.8.0" + "@jest/core" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" chalk "^2.0.1" exit "^0.1.2" import-local "^2.0.0" is-ci "^2.0.0" - jest-config "^24.8.0" - jest-util "^24.8.0" - jest-validate "^24.8.0" + jest-config "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" prompts "^2.0.1" realpath-native "^1.1.0" - yargs "^12.0.2" + yargs "^13.3.0" -jest-config@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.8.0.tgz#77db3d265a6f726294687cbbccc36f8a76ee0f4f" - integrity sha512-Czl3Nn2uEzVGsOeaewGWoDPD8GStxCpAe0zOYs2x2l0fZAgPbCr3uwUkgNKV3LwE13VXythM946cd5rdGkkBZw== +jest-config@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.9.0.tgz#fb1bbc60c73a46af03590719efa4825e6e4dd1b5" + integrity sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ== dependencies: "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^24.8.0" - "@jest/types" "^24.8.0" - babel-jest "^24.8.0" + "@jest/test-sequencer" "^24.9.0" + "@jest/types" "^24.9.0" + babel-jest "^24.9.0" chalk "^2.0.1" glob "^7.1.1" - jest-environment-jsdom "^24.8.0" - jest-environment-node "^24.8.0" - jest-get-type "^24.8.0" - jest-jasmine2 "^24.8.0" + jest-environment-jsdom "^24.9.0" + jest-environment-node "^24.9.0" + jest-get-type "^24.9.0" + jest-jasmine2 "^24.9.0" jest-regex-util "^24.3.0" - jest-resolve "^24.8.0" - jest-util "^24.8.0" - jest-validate "^24.8.0" + jest-resolve "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" micromatch "^3.1.10" - pretty-format "^24.8.0" + pretty-format "^24.9.0" realpath-native "^1.1.0" -jest-diff@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.8.0.tgz#146435e7d1e3ffdf293d53ff97e193f1d1546172" - integrity sha512-wxetCEl49zUpJ/bvUmIFjd/o52J+yWcoc5ZyPq4/W1LUKGEhRYDIbP1KcF6t+PvqNrGAFk4/JhtxDq/Nnzs66g== +jest-diff@^24.3.0, jest-diff@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da" + integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ== dependencies: chalk "^2.0.1" - diff-sequences "^24.3.0" - jest-get-type "^24.8.0" - pretty-format "^24.8.0" + diff-sequences "^24.9.0" + jest-get-type "^24.9.0" + pretty-format "^24.9.0" jest-docblock@^24.3.0: - version "24.3.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.3.0.tgz#b9c32dac70f72e4464520d2ba4aec02ab14db5dd" - integrity sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg== + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2" + integrity sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA== dependencies: detect-newline "^2.1.0" -jest-each@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.8.0.tgz#a05fd2bf94ddc0b1da66c6d13ec2457f35e52775" - integrity sha512-NrwK9gaL5+XgrgoCsd9svsoWdVkK4gnvyhcpzd6m487tXHqIdYeykgq3MKI1u4I+5Zf0tofr70at9dWJDeb+BA== +jest-each@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.9.0.tgz#eb2da602e2a610898dbc5f1f6df3ba86b55f8b05" + integrity sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog== dependencies: - "@jest/types" "^24.8.0" + "@jest/types" "^24.9.0" chalk "^2.0.1" - jest-get-type "^24.8.0" - jest-util "^24.8.0" - pretty-format "^24.8.0" - -jest-environment-jsdom@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.8.0.tgz#300f6949a146cabe1c9357ad9e9ecf9f43f38857" - integrity sha512-qbvgLmR7PpwjoFjM/sbuqHJt/NCkviuq9vus9NBn/76hhSidO+Z6Bn9tU8friecegbJL8gzZQEMZBQlFWDCwAQ== - dependencies: - "@jest/environment" "^24.8.0" - "@jest/fake-timers" "^24.8.0" - "@jest/types" "^24.8.0" - jest-mock "^24.8.0" - jest-util "^24.8.0" + jest-get-type "^24.9.0" + jest-util "^24.9.0" + pretty-format "^24.9.0" + +jest-environment-jsdom@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b" + integrity sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA== + dependencies: + "@jest/environment" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + jest-util "^24.9.0" jsdom "^11.5.1" -jest-environment-node@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.8.0.tgz#d3f726ba8bc53087a60e7a84ca08883a4c892231" - integrity sha512-vIGUEScd1cdDgR6sqn2M08sJTRLQp6Dk/eIkCeO4PFHxZMOgy+uYLPMC4ix3PEfM5Au/x3uQ/5Tl0DpXXZsJ/Q== +jest-environment-node@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.9.0.tgz#333d2d2796f9687f2aeebf0742b519f33c1cbfd3" + integrity sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA== dependencies: - "@jest/environment" "^24.8.0" - "@jest/fake-timers" "^24.8.0" - "@jest/types" "^24.8.0" - jest-mock "^24.8.0" - jest-util "^24.8.0" + "@jest/environment" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + jest-util "^24.9.0" -jest-get-type@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.8.0.tgz#a7440de30b651f5a70ea3ed7ff073a32dfe646fc" - integrity sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ== +jest-get-type@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" + integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== -jest-haste-map@^24.8.0: - version "24.8.1" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.8.1.tgz#f39cc1d2b1d907e014165b4bd5a957afcb992982" - integrity sha512-SwaxMGVdAZk3ernAx2Uv2sorA7jm3Kx+lR0grp6rMmnY06Kn/urtKx1LPN2mGTea4fCT38impYT28FfcLUhX0g== +jest-haste-map@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" + integrity sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ== dependencies: - "@jest/types" "^24.8.0" + "@jest/types" "^24.9.0" anymatch "^2.0.0" fb-watchman "^2.0.0" graceful-fs "^4.1.15" invariant "^2.2.4" - jest-serializer "^24.4.0" - jest-util "^24.8.0" - jest-worker "^24.6.0" + jest-serializer "^24.9.0" + jest-util "^24.9.0" + jest-worker "^24.9.0" micromatch "^3.1.10" sane "^4.0.3" walker "^1.0.7" optionalDependencies: fsevents "^1.2.7" -jest-jasmine2@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.8.0.tgz#a9c7e14c83dd77d8b15e820549ce8987cc8cd898" - integrity sha512-cEky88npEE5LKd5jPpTdDCLvKkdyklnaRycBXL6GNmpxe41F0WN44+i7lpQKa/hcbXaQ+rc9RMaM4dsebrYong== +jest-jasmine2@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz#1f7b1bd3242c1774e62acabb3646d96afc3be6a0" + integrity sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw== dependencies: "@babel/traverse" "^7.1.0" - "@jest/environment" "^24.8.0" - "@jest/test-result" "^24.8.0" - "@jest/types" "^24.8.0" + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" chalk "^2.0.1" co "^4.6.0" - expect "^24.8.0" + expect "^24.9.0" is-generator-fn "^2.0.0" - jest-each "^24.8.0" - jest-matcher-utils "^24.8.0" - jest-message-util "^24.8.0" - jest-runtime "^24.8.0" - jest-snapshot "^24.8.0" - jest-util "^24.8.0" - pretty-format "^24.8.0" + jest-each "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-runtime "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + pretty-format "^24.9.0" throat "^4.0.0" -jest-leak-detector@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.8.0.tgz#c0086384e1f650c2d8348095df769f29b48e6980" - integrity sha512-cG0yRSK8A831LN8lIHxI3AblB40uhv0z+SsQdW3GoMMVcK+sJwrIIyax5tu3eHHNJ8Fu6IMDpnLda2jhn2pD/g== +jest-leak-detector@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz#b665dea7c77100c5c4f7dfcb153b65cf07dcf96a" + integrity sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA== dependencies: - pretty-format "^24.8.0" + jest-get-type "^24.9.0" + pretty-format "^24.9.0" -jest-matcher-utils@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz#2bce42204c9af12bde46f83dc839efe8be832495" - integrity sha512-lex1yASY51FvUuHgm0GOVj7DCYEouWSlIYmCW7APSqB9v8mXmKSn5+sWVF0MhuASG0bnYY106/49JU1FZNl5hw== +jest-matcher-utils@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073" + integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA== dependencies: chalk "^2.0.1" - jest-diff "^24.8.0" - jest-get-type "^24.8.0" - pretty-format "^24.8.0" + jest-diff "^24.9.0" + jest-get-type "^24.9.0" + pretty-format "^24.9.0" -jest-message-util@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.8.0.tgz#0d6891e72a4beacc0292b638685df42e28d6218b" - integrity sha512-p2k71rf/b6ns8btdB0uVdljWo9h0ovpnEe05ZKWceQGfXYr4KkzgKo3PBi8wdnd9OtNh46VpNIJynUn/3MKm1g== +jest-message-util@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3" + integrity sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw== dependencies: "@babel/code-frame" "^7.0.0" - "@jest/test-result" "^24.8.0" - "@jest/types" "^24.8.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" "@types/stack-utils" "^1.0.1" chalk "^2.0.1" micromatch "^3.1.10" slash "^2.0.0" stack-utils "^1.0.1" -jest-mock@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.8.0.tgz#2f9d14d37699e863f1febf4e4d5a33b7fdbbde56" - integrity sha512-6kWugwjGjJw+ZkK4mDa0Df3sDlUTsV47MSrT0nGQ0RBWJbpODDQ8MHDVtGtUYBne3IwZUhtB7elxHspU79WH3A== +jest-mock@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6" + integrity sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w== dependencies: - "@jest/types" "^24.8.0" + "@jest/types" "^24.9.0" jest-pnp-resolver@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ== -jest-regex-util@^24.3.0: - version "24.3.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.3.0.tgz#d5a65f60be1ae3e310d5214a0307581995227b36" - integrity sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg== +jest-regex-util@^24.3.0, jest-regex-util@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.9.0.tgz#c13fb3380bde22bf6575432c493ea8fe37965636" + integrity sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA== -jest-resolve-dependencies@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.8.0.tgz#19eec3241f2045d3f990dba331d0d7526acff8e0" - integrity sha512-hyK1qfIf/krV+fSNyhyJeq3elVMhK9Eijlwy+j5jqmZ9QsxwKBiP6qukQxaHtK8k6zql/KYWwCTQ+fDGTIJauw== +jest-resolve-dependencies@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz#ad055198959c4cfba8a4f066c673a3f0786507ab" + integrity sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g== dependencies: - "@jest/types" "^24.8.0" + "@jest/types" "^24.9.0" jest-regex-util "^24.3.0" - jest-snapshot "^24.8.0" + jest-snapshot "^24.9.0" -jest-resolve@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.8.0.tgz#84b8e5408c1f6a11539793e2b5feb1b6e722439f" - integrity sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw== +jest-resolve@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.9.0.tgz#dff04c7687af34c4dd7e524892d9cf77e5d17321" + integrity sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ== dependencies: - "@jest/types" "^24.8.0" + "@jest/types" "^24.9.0" browser-resolve "^1.11.3" chalk "^2.0.1" jest-pnp-resolver "^1.2.1" realpath-native "^1.1.0" -jest-runner@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.8.0.tgz#4f9ae07b767db27b740d7deffad0cf67ccb4c5bb" - integrity sha512-utFqC5BaA3JmznbissSs95X1ZF+d+4WuOWwpM9+Ak356YtMhHE/GXUondZdcyAAOTBEsRGAgH/0TwLzfI9h7ow== +jest-runner@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.9.0.tgz#574fafdbd54455c2b34b4bdf4365a23857fcdf42" + integrity sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg== dependencies: "@jest/console" "^24.7.1" - "@jest/environment" "^24.8.0" - "@jest/test-result" "^24.8.0" - "@jest/types" "^24.8.0" + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" chalk "^2.4.2" exit "^0.1.2" graceful-fs "^4.1.15" - jest-config "^24.8.0" + jest-config "^24.9.0" jest-docblock "^24.3.0" - jest-haste-map "^24.8.0" - jest-jasmine2 "^24.8.0" - jest-leak-detector "^24.8.0" - jest-message-util "^24.8.0" - jest-resolve "^24.8.0" - jest-runtime "^24.8.0" - jest-util "^24.8.0" + jest-haste-map "^24.9.0" + jest-jasmine2 "^24.9.0" + jest-leak-detector "^24.9.0" + jest-message-util "^24.9.0" + jest-resolve "^24.9.0" + jest-runtime "^24.9.0" + jest-util "^24.9.0" jest-worker "^24.6.0" source-map-support "^0.5.6" throat "^4.0.0" -jest-runtime@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.8.0.tgz#05f94d5b05c21f6dc54e427cd2e4980923350620" - integrity sha512-Mq0aIXhvO/3bX44ccT+czU1/57IgOMyy80oM0XR/nyD5zgBcesF84BPabZi39pJVA6UXw+fY2Q1N+4BiVUBWOA== +jest-runtime@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.9.0.tgz#9f14583af6a4f7314a6a9d9f0226e1a781c8e4ac" + integrity sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw== dependencies: "@jest/console" "^24.7.1" - "@jest/environment" "^24.8.0" + "@jest/environment" "^24.9.0" "@jest/source-map" "^24.3.0" - "@jest/transform" "^24.8.0" - "@jest/types" "^24.8.0" - "@types/yargs" "^12.0.2" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/yargs" "^13.0.0" chalk "^2.0.1" exit "^0.1.2" glob "^7.1.3" graceful-fs "^4.1.15" - jest-config "^24.8.0" - jest-haste-map "^24.8.0" - jest-message-util "^24.8.0" - jest-mock "^24.8.0" + jest-config "^24.9.0" + jest-haste-map "^24.9.0" + jest-message-util "^24.9.0" + jest-mock "^24.9.0" jest-regex-util "^24.3.0" - jest-resolve "^24.8.0" - jest-snapshot "^24.8.0" - jest-util "^24.8.0" - jest-validate "^24.8.0" + jest-resolve "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" realpath-native "^1.1.0" slash "^2.0.0" strip-bom "^3.0.0" - yargs "^12.0.2" + yargs "^13.3.0" -jest-serializer@^24.4.0: - version "24.4.0" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.4.0.tgz#f70c5918c8ea9235ccb1276d232e459080588db3" - integrity sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q== +jest-serializer@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.9.0.tgz#e6d7d7ef96d31e8b9079a714754c5d5c58288e73" + integrity sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ== -jest-snapshot@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.8.0.tgz#3bec6a59da2ff7bc7d097a853fb67f9d415cb7c6" - integrity sha512-5ehtWoc8oU9/cAPe6fez6QofVJLBKyqkY2+TlKTOf0VllBB/mqUNdARdcjlZrs9F1Cv+/HKoCS/BknT0+tmfPg== +jest-snapshot@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.9.0.tgz#ec8e9ca4f2ec0c5c87ae8f925cf97497b0e951ba" + integrity sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew== dependencies: "@babel/types" "^7.0.0" - "@jest/types" "^24.8.0" + "@jest/types" "^24.9.0" chalk "^2.0.1" - expect "^24.8.0" - jest-diff "^24.8.0" - jest-matcher-utils "^24.8.0" - jest-message-util "^24.8.0" - jest-resolve "^24.8.0" + expect "^24.9.0" + jest-diff "^24.9.0" + jest-get-type "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-resolve "^24.9.0" mkdirp "^0.5.1" natural-compare "^1.4.0" - pretty-format "^24.8.0" - semver "^5.5.0" - -jest-util@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.8.0.tgz#41f0e945da11df44cc76d64ffb915d0716f46cd1" - integrity sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA== - dependencies: - "@jest/console" "^24.7.1" - "@jest/fake-timers" "^24.8.0" - "@jest/source-map" "^24.3.0" - "@jest/test-result" "^24.8.0" - "@jest/types" "^24.8.0" + pretty-format "^24.9.0" + semver "^6.2.0" + +jest-util@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162" + integrity sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg== + dependencies: + "@jest/console" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/source-map" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" callsites "^3.0.0" chalk "^2.0.1" graceful-fs "^4.1.15" @@ -3034,46 +2987,46 @@ jest-util@^24.8.0: slash "^2.0.0" source-map "^0.6.0" -jest-validate@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.8.0.tgz#624c41533e6dfe356ffadc6e2423a35c2d3b4849" - integrity sha512-+/N7VOEMW1Vzsrk3UWBDYTExTPwf68tavEPKDnJzrC6UlHtUDU/fuEdXqFoHzv9XnQ+zW6X3qMZhJ3YexfeLDA== +jest-validate@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.9.0.tgz#0775c55360d173cd854e40180756d4ff52def8ab" + integrity sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ== dependencies: - "@jest/types" "^24.8.0" - camelcase "^5.0.0" + "@jest/types" "^24.9.0" + camelcase "^5.3.1" chalk "^2.0.1" - jest-get-type "^24.8.0" - leven "^2.1.0" - pretty-format "^24.8.0" - -jest-watcher@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.8.0.tgz#58d49915ceddd2de85e238f6213cef1c93715de4" - integrity sha512-SBjwHt5NedQoVu54M5GEx7cl7IGEFFznvd/HNT8ier7cCAx/Qgu9ZMlaTQkvK22G1YOpcWBLQPFSImmxdn3DAw== - dependencies: - "@jest/test-result" "^24.8.0" - "@jest/types" "^24.8.0" - "@types/yargs" "^12.0.9" + jest-get-type "^24.9.0" + leven "^3.1.0" + pretty-format "^24.9.0" + +jest-watcher@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.9.0.tgz#4b56e5d1ceff005f5b88e528dc9afc8dd4ed2b3b" + integrity sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw== + dependencies: + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/yargs" "^13.0.0" ansi-escapes "^3.0.0" chalk "^2.0.1" - jest-util "^24.8.0" + jest-util "^24.9.0" string-length "^2.0.0" -jest-worker@^24.6.0: - version "24.6.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.6.0.tgz#7f81ceae34b7cde0c9827a6980c35b7cdc0161b3" - integrity sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ== +jest-worker@^24.6.0, jest-worker@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5" + integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== dependencies: - merge-stream "^1.0.1" + merge-stream "^2.0.0" supports-color "^6.1.0" -jest@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-24.8.0.tgz#d5dff1984d0d1002196e9b7f12f75af1b2809081" - integrity sha512-o0HM90RKFRNWmAWvlyV8i5jGZ97pFwkeVoGvPW1EtLTgJc2+jcuqcbbqcSZLE/3f2S5pt0y2ZBETuhpWNl1Reg== +jest@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-24.9.0.tgz#987d290c05a08b52c56188c1002e368edb007171" + integrity sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw== dependencies: import-local "^2.0.0" - jest-cli "^24.8.0" + jest-cli "^24.9.0" "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" @@ -3155,12 +3108,12 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json5@2.x, json5@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850" - integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ== +json5@2.x, json5@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" + integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== dependencies: - minimist "^1.2.0" + minimist "^1.2.5" json5@^1.0.1: version "1.0.1" @@ -3199,14 +3152,14 @@ kind-of@^5.0.0: integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -kleur@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.2.tgz#83c7ec858a41098b613d5998a7b653962b504f68" - integrity sha512-3h7B2WRT5LNXOtQiAaWonilegHcPSf9nLVXlSTci8lu1dZUuui61+EsPEZqSVxY7rXYmB2DVKMQILxaO5WL61Q== +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== lcid@^2.0.0: version "2.0.0" @@ -3220,10 +3173,10 @@ left-pad@^1.3.0: resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA== -leven@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" - integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA= +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== levn@^0.3.0, levn@~0.3.0: version "0.3.0" @@ -3243,12 +3196,12 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" -loader-runner@^2.3.0: +loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.1.0: +loader-utils@1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== @@ -3257,6 +3210,15 @@ loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.1.0: emojis-list "^2.0.0" json5 "^1.0.1" +loader-utils@^1.0.2, loader-utils@^1.2.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -3265,6 +3227,11 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -3275,7 +3242,7 @@ lodash.unescape@4.0.1: resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw= -lodash@^4.17.10, lodash@^4.17.11: +lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -3294,17 +3261,18 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -make-dir@^1.0.0, make-dir@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" - integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== +make-dir@^2.0.0, make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== dependencies: - pify "^3.0.0" + pify "^4.0.1" + semver "^5.6.0" make-error@1.x: - version "1.3.5" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" - integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g== + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== makeerror@1.0.x: version "1.0.11" @@ -3313,11 +3281,6 @@ makeerror@1.0.x: dependencies: tmpl "1.0.x" -mamacro@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" - integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA== - map-age-cleaner@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" @@ -3347,15 +3310,15 @@ md5.js@^1.3.4: safe-buffer "^5.1.2" mem@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.1.0.tgz#aeb9be2d21f47e78af29e4ac5978e8afa2ca5b8a" - integrity sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg== + version "4.3.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" + integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== dependencies: map-age-cleaner "^0.1.1" - mimic-fn "^1.0.0" + mimic-fn "^2.0.0" p-is-promise "^2.0.0" -memory-fs@^0.4.0, memory-fs@~0.4.1: +memory-fs@^0.4.0, memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -3363,14 +3326,20 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" -merge-stream@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" - integrity sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE= +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== dependencies: + errno "^0.1.3" readable-stream "^2.0.1" -micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -3397,23 +3366,28 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@~1.38.0: - version "1.38.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" - integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.22" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.22.tgz#fe6b355a190926ab7698c9a0556a11199b2199bd" - integrity sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog== + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== dependencies: - mime-db "~1.38.0" + mime-db "1.43.0" mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== +mimic-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -3431,35 +3405,10 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -minimist@^1.1.1, minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= - -minipass@^2.2.1, minipass@^2.3.4: - version "2.3.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" - integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" - integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== - dependencies: - minipass "^2.2.1" +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== mississippi@^3.0.0: version "3.0.0" @@ -3485,12 +3434,12 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= +mkdirp@0.x, mkdirp@^0.5.1, mkdirp@^0.5.3: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: - minimist "0.0.8" + minimist "^1.2.5" move-concurrently@^1.0.1: version "1.0.1" @@ -3510,9 +3459,9 @@ ms@2.0.0: integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= ms@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== msgpack5@^4.0.2: version "4.2.1" @@ -3529,10 +3478,10 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nan@^2.9.2: - version "2.12.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" - integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== +nan@^2.12.1: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== nanomatch@^1.2.9: version "1.2.13" @@ -3556,21 +3505,7 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -needle@^2.2.1: - version "2.2.4" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" - integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA== - dependencies: - debug "^2.1.2" - iconv-lite "^0.4.4" - sax "^1.2.4" - -neo-async@^2.5.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835" - integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA== - -neo-async@^2.6.0: +neo-async@^2.5.0, neo-async@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== @@ -3585,10 +3520,10 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= -node-libs-browser@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.0.tgz#c72f60d9d46de08a940dedbb25f3ffa2f9bbaa77" - integrity sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA== +node-libs-browser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== dependencies: assert "^1.1.1" browserify-zlib "^0.2.0" @@ -3600,7 +3535,7 @@ node-libs-browser@^2.0.0: events "^3.0.0" https-browserify "^1.0.0" os-browserify "^0.3.0" - path-browserify "0.0.0" + path-browserify "0.0.1" process "^0.11.10" punycode "^1.2.4" querystring-es3 "^0.2.0" @@ -3612,17 +3547,17 @@ node-libs-browser@^2.0.0: tty-browserify "0.0.0" url "^0.11.0" util "^0.11.0" - vm-browserify "0.0.4" + vm-browserify "^1.0.1" node-modules-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= -node-notifier@^5.2.1: - version "5.4.0" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.0.tgz#7b455fdce9f7de0c63538297354f3db468426e6a" - integrity sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ== +node-notifier@^5.4.2: + version "5.4.3" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.3.tgz#cb72daf94c93904098e28b9c590fd866e464bd50" + integrity sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q== dependencies: growly "^1.3.0" is-wsl "^1.1.0" @@ -3630,30 +3565,6 @@ node-notifier@^5.2.1: shellwords "^0.1.1" which "^1.3.0" -node-pre-gyp@^0.10.0: - version "0.10.3" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" - integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= - dependencies: - abbrev "1" - osenv "^0.1.4" - normalize-package-data@^2.3.2: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -3676,19 +3587,6 @@ normalize-path@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-bundled@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" - integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== - -npm-packlist@^1.1.6: - version "1.4.1" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc" - integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -3696,32 +3594,17 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - nwsapi@^2.0.7: - version "2.1.0" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.1.0.tgz#781065940aed90d9bb01ca5d0ce0fcf81c32712f" - integrity sha512-ZG3bLAvdHmhIjaQ/Db1qvBxsGvFMLIRpQszyqbg31VJ53UP++uZX1/gf3Ut96pdwN9AuDwlMqIYLm0UPCdUeHg== + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.1.0: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -3735,10 +3618,15 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-keys@^1.0.12: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032" - integrity sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg== +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== object-visit@^1.0.0: version "1.0.1" @@ -3747,13 +3635,23 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.getownpropertydescriptors@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" - integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== dependencies: define-properties "^1.1.2" - es-abstract "^1.5.1" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.getownpropertydescriptors@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" object.pick@^1.3.0: version "1.3.0" @@ -3776,25 +3674,17 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -optimist@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" - optionator@^0.8.1, optionator@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" - integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== dependencies: deep-is "~0.1.3" - fast-levenshtein "~2.0.4" + fast-levenshtein "~2.0.6" levn "~0.3.0" prelude-ls "~1.1.2" type-check "~0.3.2" - wordwrap "~1.0.0" + word-wrap "~1.2.3" original@^1.0.0: version "1.0.2" @@ -3808,12 +3698,7 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-locale@^3.0.0, os-locale@^3.1.0: +os-locale@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== @@ -3822,19 +3707,11 @@ os-locale@^3.0.0, os-locale@^3.1.0: lcid "^2.0.0" mem "^4.0.0" -os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: +os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -3853,14 +3730,14 @@ p-finally@^1.0.0: integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= p-is-promise@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.0.0.tgz#7554e3d572109a87e1f3f53f6a7d85d1b194f4c5" - integrity sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg== + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" + integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== p-limit@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.1.0.tgz#1d5a0d20fb12707c758a655f6bbc4386b5930d68" - integrity sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g== + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" @@ -3877,21 +3754,21 @@ p-reduce@^1.0.0: integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= p-try@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" - integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ== + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== pako@~1.0.5: - version "1.0.8" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.8.tgz#6844890aab9c635af868ad5fecc62e8acbba3ea4" - integrity sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA== + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== parallel-transform@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06" - integrity sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY= + version "1.2.0" + resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" + integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== dependencies: - cyclist "~0.2.2" + cyclist "^1.0.1" inherits "^2.0.3" readable-stream "^2.1.5" @@ -3903,9 +3780,9 @@ parent-module@^1.0.0: callsites "^3.0.0" parse-asn1@^5.0.0: - version "5.1.4" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc" - integrity sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw== + version "5.1.5" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" + integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== dependencies: asn1.js "^4.0.0" browserify-aes "^1.0.0" @@ -3937,10 +3814,10 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -path-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" - integrity sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo= +path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== path-dirname@^1.0.0: version "1.0.2" @@ -4000,6 +3877,11 @@ pify@^3.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + pirates@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" @@ -4029,20 +3911,20 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -pretty-format@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2" - integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw== +pretty-format@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" + integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== dependencies: - "@jest/types" "^24.8.0" + "@jest/types" "^24.9.0" ansi-regex "^4.0.0" ansi-styles "^3.2.0" react-is "^16.8.4" process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== process@^0.11.10: version "0.11.10" @@ -4060,22 +3942,22 @@ promise-inflight@^1.0.1: integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= prompts@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.0.3.tgz#c5ccb324010b2e8f74752aadceeb57134c1d2522" - integrity sha512-H8oWEoRZpybm6NV4to9/1limhttEo13xK62pNvn2JzY0MA03p7s0OjtmhXyon3uJmxiJJVSuUwEJFFssI3eBiQ== + version "2.3.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" + integrity sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA== dependencies: - kleur "^3.0.2" - sisteransi "^1.0.0" + kleur "^3.0.3" + sisteransi "^1.0.4" prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= -psl@^1.1.24, psl@^1.1.28: - version "1.1.31" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" - integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== public-encrypt@^4.0.0: version "4.0.3" @@ -4119,7 +4001,7 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.2.4, punycode@^1.4.1: +punycode@^1.2.4: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -4144,10 +4026,10 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= -querystringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef" - integrity sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg== +querystringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" + integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.1.0" @@ -4164,20 +4046,10 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - react-is@^16.8.4: - version "16.8.6" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" - integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== read-pkg-up@^4.0.0: version "4.0.0" @@ -4196,10 +4068,10 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -4253,26 +4125,26 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= -request-promise-core@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.2.tgz#339f6aababcafdb31c799ff158700336301d3346" - integrity sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag== +request-promise-core@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" + integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== dependencies: - lodash "^4.17.11" + lodash "^4.17.15" request-promise-native@^1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.7.tgz#a49868a624bdea5069f1251d0a836e0d89aa2c59" - integrity sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w== + version "1.0.8" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" + integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== dependencies: - request-promise-core "1.1.2" + request-promise-core "1.1.3" stealthy-require "^1.1.1" tough-cookie "^2.3.3" request@^2.87.0, request@^2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -4281,7 +4153,7 @@ request@^2.87.0, request@^2.88.0: extend "~3.0.2" forever-agent "~0.6.1" form-data "~2.3.2" - har-validator "~5.1.0" + har-validator "~5.1.3" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" @@ -4291,7 +4163,7 @@ request@^2.87.0, request@^2.88.0: performance-now "^2.1.0" qs "~6.5.2" safe-buffer "^5.1.2" - tough-cookie "~2.4.3" + tough-cookie "~2.5.0" tunnel-agent "^0.6.0" uuid "^3.3.2" @@ -4300,21 +4172,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= - require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -requireindex@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" - integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== - requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -4356,9 +4218,9 @@ resolve@1.1.7: integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= resolve@1.x, resolve@^1.10.0, resolve@^1.3.2: - version "1.10.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" - integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== + version "1.16.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.16.0.tgz#063dc704fa3413e13ac1d0d1756a7cbfe95dd1a7" + integrity sha512-LarL/PIKJvc09k1jaeT4kQb/8/7P+qV4qSnN2K80AES+OHdfZELAKVOBjxsvtToT/uLOfFbvYvKfZmV8cee7nA== dependencies: path-parse "^1.0.6" @@ -4375,13 +4237,20 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -rimraf@2.6.3, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: +rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== dependencies: glob "^7.1.3" +rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -4396,9 +4265,9 @@ rsvp@^4.8.4: integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== run-async@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" - integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= + version "2.4.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.0.tgz#e59054a5b86876cfae07f431d18cbaddc594f1e8" + integrity sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg== dependencies: is-promise "^2.1.0" @@ -4410,13 +4279,18 @@ run-queue@^1.0.0, run-queue@^1.0.3: aproba "^1.1.1" rxjs@^6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504" - integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw== + version "6.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" + integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== dependencies: tslib "^1.9.0" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -4462,32 +4336,32 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" - integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== +"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== semver@5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== -semver@^5.5.1: - version "5.7.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" - integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== +semver@^6.0.0, semver@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -serialize-javascript@^1.4.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879" - integrity sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw== +serialize-javascript@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" + integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== -set-blocking@^2.0.0, set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -set-value@^0.4.3, set-value@^2.0.0, set-value@^2.0.1: +set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== @@ -4528,14 +4402,14 @@ shellwords@^0.1.1: integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== -sisteransi@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.0.tgz#77d9622ff909080f1c19e5f4a1df0c1b0a27b88c" - integrity sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ== +sisteransi@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== slash@^2.0.0: version "2.0.0" @@ -4587,20 +4461,20 @@ source-list-map@^2.0.0: integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== source-map-resolve@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" - integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== dependencies: - atob "^2.1.1" + atob "^2.1.2" decode-uri-component "^0.2.0" resolve-url "^0.2.1" source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.5.6, source-map-support@~0.5.9: - version "0.5.10" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c" - integrity sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ== +source-map-support@^0.5.6, source-map-support@~0.5.12: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -4642,9 +4516,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e" - integrity sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g== + version "3.0.5" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" + integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -4726,9 +4600,9 @@ stream-http@^2.7.2: xtend "^4.0.0" stream-shift@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" - integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== string-length@^2.0.0: version "2.0.0" @@ -4738,16 +4612,7 @@ string-length@^2.0.0: astral-regex "^1.0.0" strip-ansi "^4.0.0" -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: +string-width@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -4764,12 +4629,46 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string.prototype.trimend@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trimleft@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" + integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.trimstart "^1.0.0" + +string.prototype.trimright@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" + integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.trimend "^1.0.0" + +string.prototype.trimstart@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string_decoder@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" - integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: - safe-buffer "~5.1.0" + safe-buffer "~5.2.0" string_decoder@~1.1.1: version "1.1.1" @@ -4778,13 +4677,6 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -4792,14 +4684,7 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.0.0.tgz#f78f68b5d0866c20b2c9b8c61b5298508dc8756f" - integrity sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow== - dependencies: - ansi-regex "^4.0.0" - -strip-ansi@^5.1.0, strip-ansi@^5.2.0: +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== @@ -4816,12 +4701,12 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= -strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: +strip-json-comments@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -supports-color@6.1.0, supports-color@^6.0.0, supports-color@^6.1.0: +supports-color@6.1.0, supports-color@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== @@ -4836,70 +4721,58 @@ supports-color@^5.3.0: has-flag "^3.0.0" symbol-tree@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" - integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY= + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== table@^5.2.3: - version "5.2.3" - resolved "https://registry.yarnpkg.com/table/-/table-5.2.3.tgz#cde0cc6eb06751c009efab27e8c820ca5b67b7f2" - integrity sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ== + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== dependencies: - ajv "^6.9.1" - lodash "^4.17.11" + ajv "^6.10.2" + lodash "^4.17.14" slice-ansi "^2.1.0" string-width "^3.0.0" -tapable@^1.0.0, tapable@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.1.tgz#4d297923c5a72a42360de2ab52dadfaaec00018e" - integrity sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA== - -tar@^4: - version "4.4.8" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" - integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.3.4" - minizlib "^1.1.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.2" +tapable@^1.0.0, tapable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -terser-webpack-plugin@^1.1.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.2.tgz#9bff3a891ad614855a7dde0d707f7db5a927e3d9" - integrity sha512-1DMkTk286BzmfylAvLXwpJrI7dWa5BnFmscV/2dCr8+c56egFcbaeFAl7+sujAjdmpLam21XRdhA4oifLyiWWg== +terser-webpack-plugin@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" + integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA== dependencies: - cacache "^11.0.2" - find-cache-dir "^2.0.0" + cacache "^12.0.2" + find-cache-dir "^2.1.0" + is-wsl "^1.1.0" schema-utils "^1.0.0" - serialize-javascript "^1.4.0" + serialize-javascript "^2.1.2" source-map "^0.6.1" - terser "^3.16.1" - webpack-sources "^1.1.0" - worker-farm "^1.5.2" + terser "^4.1.2" + webpack-sources "^1.4.0" + worker-farm "^1.7.0" -terser@^3.16.1: - version "3.16.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-3.16.1.tgz#5b0dd4fa1ffd0b0b43c2493b2c364fd179160493" - integrity sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow== +terser@^4.1.2: + version "4.6.11" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.11.tgz#12ff99fdd62a26de2a82f508515407eb6ccd8a9f" + integrity sha512-76Ynm7OXUG5xhOpblhytE7X58oeNSmC8xnNhjWVo8CksHit0U0kO4hfNbPrrYwowLWFgM2n9L176VNx2QaHmtA== dependencies: - commander "~2.17.1" + commander "^2.20.0" source-map "~0.6.1" - source-map-support "~0.5.9" + source-map-support "~0.5.12" -test-exclude@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.1.0.tgz#6ba6b25179d2d38724824661323b73e03c0c1de1" - integrity sha512-gwf0S2fFsANC55fSeSqpb8BYk6w3FDvwZxfNjeF6FRgvFa43r+7wRiA/Q0IxoRU37wB/LE8IQ4221BsNucTaCA== +test-exclude@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" + integrity sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g== dependencies: - arrify "^1.0.1" + glob "^7.1.3" minimatch "^3.0.4" read-pkg-up "^4.0.0" - require-main-filename "^1.0.1" + require-main-filename "^2.0.0" text-table@^0.2.0: version "0.2.0" @@ -4925,9 +4798,9 @@ through@^2.3.6: integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= timers-browserify@^2.0.4: - version "2.0.10" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" - integrity sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg== + version "2.0.11" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" + integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== dependencies: setimmediate "^1.0.4" @@ -4978,7 +4851,7 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" -tough-cookie@^2.3.3, tough-cookie@^2.3.4: +tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -4986,14 +4859,6 @@ tough-cookie@^2.3.3, tough-cookie@^2.3.4: psl "^1.1.28" punycode "^2.1.1" -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== - dependencies: - psl "^1.1.24" - punycode "^1.4.1" - tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -5001,20 +4866,16 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= - -ts-jest@^24.0.0: - version "24.0.0" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.0.0.tgz#3f26bf2ec1fa584863a5a9c29bd8717d549efbf6" - integrity sha512-o8BO3TkMREpAATaFTrXkovMsCpBl2z4NDBoLJuWZcJJj1ijI49UnvDMfVpj+iogn/Jl8Pbhuei5nc/Ti+frEHw== +ts-jest@^24.3.0: + version "24.3.0" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.3.0.tgz#b97814e3eab359ea840a1ac112deae68aa440869" + integrity sha512-Hb94C/+QRIgjVZlJyiWwouYUF+siNJHJHknyspaOcZ+OQAIdFG/UrdQVXw/0B8Z3No34xkUXZJpOTy9alOWdVQ== dependencies: bs-logger "0.x" buffer-from "1.x" fast-json-stable-stringify "2.x" json5 "2.x" + lodash.memoize "4.x" make-error "1.x" mkdirp "0.x" resolve "1.x" @@ -5033,14 +4894,14 @@ ts-loader@^4.4.1: semver "^5.0.1" tslib@^1.8.1, tslib@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" - integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== + version "1.11.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" + integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== tsutils@^3.7.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.9.1.tgz#2a40dc742943c71eca6d5c1994fcf999956be387" - integrity sha512-hrxVtLtPqQr//p8/msPT1X1UYXUjizqSit5d9AQ5k38TcV38NyecL5xODNxa73cLe/5sdiJ+w1FqzDhRBA/anA== + version "3.17.1" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" + integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== dependencies: tslib "^1.8.1" @@ -5073,28 +4934,20 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" - integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== - -uglify-js@^3.1.4: - version "3.4.9" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" - integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q== - dependencies: - commander "~2.17.1" - source-map "~0.6.1" +typescript@^3.8.3: + version "3.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" + integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== union-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" - integrity sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ= + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== dependencies: arr-union "^3.1.0" get-value "^2.0.6" is-extendable "^0.1.1" - set-value "^0.4.3" + set-value "^2.0.1" unique-filename@^1.1.1: version "1.1.1" @@ -5104,9 +4957,9 @@ unique-filename@^1.1.1: unique-slug "^2.0.0" unique-slug@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.1.tgz#5e9edc6d1ce8fb264db18a507ef9bd8544451ca6" - integrity sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== dependencies: imurmurhash "^0.1.4" @@ -5118,10 +4971,10 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -upath@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" - integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw== +upath@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== uri-js@^4.2.2: version "4.2.2" @@ -5136,11 +4989,11 @@ urix@^0.1.0: integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= url-parse@^1.4.3: - version "1.4.4" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8" - integrity sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg== + version "1.4.7" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" + integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== dependencies: - querystringify "^2.0.0" + querystringify "^2.1.1" requires-port "^1.0.0" url@^0.11.0: @@ -5162,12 +5015,14 @@ util-deprecate@~1.0.1: integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= util.promisify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" - integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== dependencies: - define-properties "^1.1.2" - object.getownpropertydescriptors "^2.0.3" + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" util@0.10.3: version "0.10.3" @@ -5184,9 +5039,9 @@ util@^0.11.0: inherits "2.0.3" uuid@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== v8-compile-cache@2.0.3: version "2.0.3" @@ -5210,19 +5065,17 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vm-browserify@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" - integrity sha1-XX6kW7755Kb/ZflUOOCofDV9WnM= - dependencies: - indexof "0.0.1" +vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== w3c-hr-time@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045" - integrity sha1-gqwr/2PZUOqeMYmlimViX+3xkEU= + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== dependencies: - browser-process-hrtime "^0.1.2" + browser-process-hrtime "^1.0.0" walker@^1.0.7, walker@~1.0.5: version "1.0.7" @@ -5231,12 +5084,12 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" -watchpack@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" - integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== +watchpack@^1.6.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.1.tgz#280da0a8718592174010c078c7585a74cd8cd0e2" + integrity sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA== dependencies: - chokidar "^2.0.2" + chokidar "^2.1.8" graceful-fs "^4.1.2" neo-async "^2.5.0" @@ -5245,10 +5098,10 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -webpack-cli@^3.3.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.6.tgz#2c8c399a2642133f8d736a359007a052e060032c" - integrity sha512-0vEa83M7kJtxK/jUhlpZ27WHIOndz5mghWL2O53kiDoA9DIxSKnfqB92LoqEn77cT4f3H2cZm1BMEat/6AZz3A== +webpack-cli@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.11.tgz#3bf21889bf597b5d82c38f215135a411edfdc631" + integrity sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g== dependencies: chalk "2.4.2" cross-spawn "6.0.5" @@ -5262,42 +5115,42 @@ webpack-cli@^3.3.6: v8-compile-cache "2.0.3" yargs "13.2.4" -webpack-sources@^1.1.0, webpack-sources@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" - integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA== +webpack-sources@^1.4.0, webpack-sources@^1.4.1: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== dependencies: source-list-map "^2.0.0" source-map "~0.6.1" -webpack@^4.36.1: - version "4.36.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.36.1.tgz#f546fda7a403a76faeaaa7196c50d12370ed18a9" - integrity sha512-Ej01/N9W8DVyhEpeQnbUdGvOECw0L46FxS12cCOs8gSK7bhUlrbHRnWkjiXckGlHjUrmL89kDpTRIkUk6Y+fKg== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-module-context" "1.8.5" - "@webassemblyjs/wasm-edit" "1.8.5" - "@webassemblyjs/wasm-parser" "1.8.5" - acorn "^6.2.0" - ajv "^6.1.0" - ajv-keywords "^3.1.0" - chrome-trace-event "^1.0.0" +webpack@^4.42.1: + version "4.42.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.42.1.tgz#ae707baf091f5ca3ef9c38b884287cfe8f1983ef" + integrity sha512-SGfYMigqEfdGchGhFFJ9KyRpQKnipvEvjc1TwrXEPCM6H5Wywu10ka8o3KGrMzSMxMQKt8aCHUFh5DaQ9UmyRg== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/wasm-edit" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + acorn "^6.2.1" + ajv "^6.10.2" + ajv-keywords "^3.4.1" + chrome-trace-event "^1.0.2" enhanced-resolve "^4.1.0" - eslint-scope "^4.0.0" + eslint-scope "^4.0.3" json-parse-better-errors "^1.0.2" - loader-runner "^2.3.0" - loader-utils "^1.1.0" - memory-fs "~0.4.1" - micromatch "^3.1.8" - mkdirp "~0.5.0" - neo-async "^2.5.0" - node-libs-browser "^2.0.0" + loader-runner "^2.4.0" + loader-utils "^1.2.3" + memory-fs "^0.4.1" + micromatch "^3.1.10" + mkdirp "^0.5.3" + neo-async "^2.6.1" + node-libs-browser "^2.2.1" schema-utils "^1.0.0" - tapable "^1.1.0" - terser-webpack-plugin "^1.1.0" - watchpack "^1.5.0" - webpack-sources "^1.3.0" + tapable "^1.1.3" + terser-webpack-plugin "^1.4.3" + watchpack "^1.6.0" + webpack-sources "^1.4.1" whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: version "1.0.5" @@ -5321,9 +5174,9 @@ whatwg-url@^6.4.1: webidl-conversions "^4.0.2" whatwg-url@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.0.0.tgz#fde926fa54a599f3adf82dff25a9f7be02dc6edd" - integrity sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ== + version "7.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" + integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== dependencies: lodash.sortby "^4.7.0" tr46 "^1.0.1" @@ -5341,38 +5194,18 @@ which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= - -wordwrap@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -worker-farm@^1.5.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0" - integrity sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ== +worker-farm@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" + integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== dependencies: errno "~0.1.7" -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" @@ -5411,9 +5244,9 @@ ws@^5.2.0: async-limiter "~1.0.0" ws@^6.0.0: - version "6.1.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" - integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== dependencies: async-limiter "~1.0.0" @@ -5423,19 +5256,19 @@ xml-name-validator@^3.0.0: integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== xtend@^4.0.0, xtend@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" - integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: +y18n@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== -yallist@^3.0.0, yallist@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" - integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== yargs-parser@10.x: version "10.1.0" @@ -5444,18 +5277,10 @@ yargs-parser@10.x: dependencies: camelcase "^4.1.0" -yargs-parser@^11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" - integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^13.1.0: - version "13.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" - integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== +yargs-parser@^13.1.0, yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" @@ -5477,20 +5302,18 @@ yargs@13.2.4: y18n "^4.0.0" yargs-parser "^13.1.0" -yargs@^12.0.2: - version "12.0.5" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" - integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== +yargs@^13.3.0: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== dependencies: - cliui "^4.0.0" - decamelize "^1.2.0" + cliui "^5.0.0" find-up "^3.0.0" - get-caller-file "^1.0.1" - os-locale "^3.0.0" + get-caller-file "^2.0.1" require-directory "^2.1.1" - require-main-filename "^1.0.1" + require-main-filename "^2.0.0" set-blocking "^2.0.0" - string-width "^2.0.0" + string-width "^3.0.0" which-module "^2.0.0" - y18n "^3.2.1 || ^4.0.0" - yargs-parser "^11.1.1" + y18n "^4.0.0" + yargs-parser "^13.1.2" diff --git a/src/Components/WebAssembly/Authentication.Msal/src/Interop/AuthenticationService.ts b/src/Components/WebAssembly/Authentication.Msal/src/Interop/AuthenticationService.ts new file mode 100644 index 0000000000000000000000000000000000000000..03838a5c9b45feb91a884381d7c33b2e238e868b --- /dev/null +++ b/src/Components/WebAssembly/Authentication.Msal/src/Interop/AuthenticationService.ts @@ -0,0 +1,355 @@ +import * as Msal from 'msal'; +import { StringDict } from 'msal/lib-commonjs/MsalTypes'; +import { ClientAuthErrorMessage } from 'msal/lib-commonjs/error/ClientAuthError'; + +interface AccessTokenRequestOptions { + scopes: string[]; + returnUrl: string; +} + +interface AccessTokenResult { + status: AccessTokenResultStatus; + token?: AccessToken; +} + +interface AccessToken { + value: string; + expires: Date; + grantedScopes: string[]; +} + +enum AccessTokenResultStatus { + Success = "success", + RequiresRedirect = "requiresRedirect" +} + +enum AuthenticationResultStatus { + Redirect = "redirect", + Success = "success", + Failure = "failure", + OperationCompleted = "operationCompleted" +} + +interface AuthenticationResult { + status: AuthenticationResultStatus; + state?: any; + message?: string; +} + +interface AuthorizeService { + getUser(): Promise<StringDict | undefined>; + getAccessToken(request?: AccessTokenRequestOptions): Promise<AccessTokenResult>; + signIn(state: any): Promise<AuthenticationResult>; + completeSignIn(state: any): Promise<AuthenticationResult>; + signOut(state: any): Promise<AuthenticationResult>; + completeSignOut(url: string): Promise<AuthenticationResult>; +} + +interface AuthorizeServiceConfiguration extends Msal.Configuration { + defaultAccessTokenScopes: string[]; + additionalScopesToConsent: string[] +} + +class MsalAuthorizeService implements AuthorizeService { + readonly _msalApplication: Msal.UserAgentApplication; + readonly _callbackPromise: Promise<AuthenticationResult>; + + constructor(private readonly _settings: AuthorizeServiceConfiguration) { + + // It is important that we capture the callback-url here as msal will remove the auth parameters + // from the url as soon as it gets initialized. + const callbackUrl = location.href; + this._msalApplication = new Msal.UserAgentApplication(this._settings); + + // This promise will only resolve in callback-paths, which is where we check it. + this._callbackPromise = this.createCallbackResult(callbackUrl); + } + + async getUser() { + const account = this._msalApplication.getAccount(); + return account?.idTokenClaims; + } + + async getAccessToken(request?: AccessTokenRequestOptions): Promise<AccessTokenResult> { + try { + const newToken = await this.getTokenCore(request?.scopes); + + return { + status: AccessTokenResultStatus.Success, + token: newToken + }; + + } catch (e) { + return { + status: AccessTokenResultStatus.RequiresRedirect + }; + } + } + + async getTokenCore(scopes?: string[]): Promise<AccessToken | undefined> { + const tokenScopes = { + redirectUri: this._settings.auth.redirectUri as string, + scopes: scopes || this._settings.defaultAccessTokenScopes + }; + + const response = await this._msalApplication.acquireTokenSilent(tokenScopes); + return { + value: response.accessToken, + grantedScopes: response.scopes, + expires: response.expiresOn + }; + } + + async signIn(state: any) { + try { + // Before we start any sign-in flow, clear out any previous state so that it doesn't pile up. + this.purgeState(); + + const request: Msal.AuthenticationParameters = { + redirectUri: this._settings.auth.redirectUri as string, + state: await this.saveState(state) + }; + + if (this._settings.defaultAccessTokenScopes && this._settings.defaultAccessTokenScopes.length > 0) { + request.scopes = this._settings.defaultAccessTokenScopes; + } + + if (this._settings.additionalScopesToConsent && this._settings.additionalScopesToConsent.length > 0) { + request.extraScopesToConsent = this._settings.additionalScopesToConsent; + } + + const result = await this.signInCore(request); + if (!result) { + return this.redirect(); + } else if (this.isMsalError(result)) { + return this.error(result.errorMessage); + } + + try { + if (this._settings.defaultAccessTokenScopes?.length > 0) { + // This provisions the token as part of the sign-in flow eagerly so that is already in the cache + // when the app asks for it. + await this._msalApplication.acquireTokenSilent(request); + } + } catch (e) { + return this.error(e.errorMessage); + } + + return this.success(state); + } catch (e) { + return this.error(e.message); + } + } + + async signInCore(request: Msal.AuthenticationParameters): Promise<Msal.AuthResponse | Msal.AuthError | undefined> { + try { + return await this._msalApplication.loginPopup(request); + } catch (e) { + // If the user explicitly cancelled the pop-up, avoid performing a redirect. + if (this.isMsalError(e) && e.errorCode !== ClientAuthErrorMessage.userCancelledError.code) { + try { + this._msalApplication.loginRedirect(request); + } catch (e) { + return e; + } + } else { + return e; + } + } + } + + completeSignIn() { + return this._callbackPromise; + } + + async signOut(state: any) { + // We are about to sign out, so clear any state before we do so and leave just the sign out state for + // the current sign out flow. + this.purgeState(); + + const logoutStateId = await this.saveState(state); + + // msal.js doesn't support providing logout state, so we shim it by putting the identifier in session storage + // and using that on the logout callback to workout the problems. + sessionStorage.setItem(`${AuthenticationService._infrastructureKey}.LogoutState`, logoutStateId); + + this._msalApplication.logout(); + + // We are about to be redirected. + return this.redirect(); + } + + async completeSignOut(url: string) { + const logoutStateId = sessionStorage.getItem(`${AuthenticationService._infrastructureKey}.LogoutState`); + const updatedUrl = new URL(url); + updatedUrl.search = `?state=${logoutStateId}`; + const logoutState = await this.retrieveState(updatedUrl.href, /*isLogout*/ true); + + sessionStorage.removeItem(`${AuthenticationService._infrastructureKey}.LogoutState`); + + if (logoutState) { + return this.success(logoutState); + } else { + return this.operationCompleted(); + } + } + + // msal.js only allows a string as the account state and it simply attaches it to the sign-in request state. + // Given that we don't want to serialize the entire state and put it in the query string, we need to serialize the + // state ourselves and pass an identifier to retrieve it while in the callback flow. + async saveState<T>(state: T): Promise<string> { + const base64UrlIdentifier = await new Promise<string>((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = evt => resolve((evt?.target?.result as string) + // The result comes back as a base64 string inside a dataUrl. + // We remove the prefix and convert it to base64url by replacing '+' with '-', '/' with '_' and removing '='. + .split(',')[1].replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')); + reader.onerror = evt => reject(evt.target?.error?.message); + + // We generate a base 64 url encoded string of random data. + const entropy = window.crypto.getRandomValues(new Uint8Array(32)); + reader.readAsDataURL(new Blob([entropy])); + }); + + sessionStorage.setItem(`${AuthenticationService._infrastructureKey}.AuthorizeService.${base64UrlIdentifier}`, JSON.stringify(state)); + return base64UrlIdentifier; + } + + async retrieveState<T>(url: string, isLogout: boolean = false): Promise<T | undefined> { + const parsedUrl = new URL(url); + const fromHash = parsedUrl.hash && parsedUrl.hash.length > 0 && new URLSearchParams(parsedUrl.hash.substring(1)); + let state = fromHash && fromHash.getAll('state'); + if (state && state.length > 1) { + return undefined; + } else if (!state || state.length == 0) { + state = parsedUrl.searchParams && parsedUrl.searchParams.getAll('state'); + if (!state || state.length !== 1) { + return undefined; + } + } + + // We need to calculate the state key in two different ways. The reason for it is that + // msal.js doesn't support the state parameter on logout flows, which forces us to shim our own logout state. + // The format then is different, as msal follows the pattern state=<<guid>>|<<user_state>> and our format + // simple uses <<base64urlIdentifier>>. + const appState = !isLogout ? this._msalApplication.getAccountState(state[0]) : state[0]; + const stateKey = `${AuthenticationService._infrastructureKey}.AuthorizeService.${appState}`; + const stateString = sessionStorage.getItem(stateKey); + if (stateString) { + sessionStorage.removeItem(stateKey); + const savedState = JSON.parse(stateString); + return savedState; + } + + return undefined; + } + + purgeState() { + for (let i = 0; i < sessionStorage.length; i++) { + const key = sessionStorage.key(i); + if (key?.startsWith(AuthenticationService._infrastructureKey)) { + sessionStorage.removeItem(key); + } + } + } + + private async createCallbackResult(callbackUrl: string): Promise<AuthenticationResult> { + // msal.js requires a callback to be registered during app initialization to handle redirect flows. + // To map that behavior to our API we register a callback early and store the result of that callback + // as a promise on an instance field to be able to serve the state back to the main app. + const promiseFactory = (resolve: (result: Msal.AuthResponse) => void, reject: (error: Msal.AuthError) => void): void => { + this._msalApplication.handleRedirectCallback( + authenticationResponse => { + resolve(authenticationResponse); + }, + authenticationError => { + reject(authenticationError); + }); + } + + try { + // Evaluate the promise to capture any authentication errors + await new Promise<Msal.AuthResponse>(promiseFactory); + // See https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/FAQs#q6-how-to-avoid-page-reloads-when-acquiring-and-renewing-tokens-silently + if (window !== window.parent && !window.opener) { + return this.operationCompleted(); + } else { + const state = await this.retrieveState(callbackUrl); + return this.success(state); + } + } catch (e) { + if (this.isMsalError(e)) { + return this.error(e.errorMessage); + } else { + return this.error(e); + } + } + } + + private isMsalError(resultOrError: any): resultOrError is Msal.AuthError { + return resultOrError?.errorCode; + } + + private error(message: string) { + return { status: AuthenticationResultStatus.Failure, errorMessage: message }; + } + + private success(state: any) { + return { status: AuthenticationResultStatus.Success, state }; + } + + private redirect() { + return { status: AuthenticationResultStatus.Redirect }; + } + + private operationCompleted() { + return { status: AuthenticationResultStatus.OperationCompleted }; + } +} + +export class AuthenticationService { + + static _infrastructureKey = 'Microsoft.Authentication.WebAssembly.Msal'; + static _initialized = false; + static instance: MsalAuthorizeService; + + public static async init(settings: AuthorizeServiceConfiguration) { + if (!AuthenticationService._initialized) { + AuthenticationService._initialized = true; + AuthenticationService.instance = new MsalAuthorizeService(settings); + } + } + + public static getUser() { + return AuthenticationService.instance.getUser(); + } + + public static getAccessToken(request: AccessTokenRequestOptions) { + return AuthenticationService.instance.getAccessToken(request); + } + + public static signIn(state: any) { + return AuthenticationService.instance.signIn(state); + } + + // url is not used in the msal.js implementation but we keep it here + // as it is part of the default RemoteAuthenticationService contract implementation. + // The unused parameter here just reflects that. + public static completeSignIn(url: string) { + return AuthenticationService.instance.completeSignIn(); + } + + public static signOut(state: any) { + return AuthenticationService.instance.signOut(state); + } + + public static completeSignOut(url: string) { + return AuthenticationService.instance.completeSignOut(url); + } +} + +declare global { + interface Window { AuthenticationService: AuthenticationService; } +} + +window.AuthenticationService = AuthenticationService; diff --git a/src/Components/WebAssembly/Authentication.Msal/src/Interop/dist/.gitignore b/src/Components/WebAssembly/Authentication.Msal/src/Interop/dist/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4e57eef8845b8583ecaec86fc7f4e8bd0eed5ac9 --- /dev/null +++ b/src/Components/WebAssembly/Authentication.Msal/src/Interop/dist/.gitignore @@ -0,0 +1,2 @@ +*.js +*.js.map diff --git a/src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json b/src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json new file mode 100644 index 0000000000000000000000000000000000000000..68a27c2e1c4644ac858742bec9eb218bdc4aeae9 --- /dev/null +++ b/src/Components/WebAssembly/Authentication.Msal/src/Interop/package.json @@ -0,0 +1,18 @@ +{ + "private": true, + "scripts": { + "build": "npm run build:release", + "build:release": "webpack --mode production --env.production --env.configuration=Release", + "build:debug": "webpack --mode development --env.configuration=Debug", + "watch": "webpack --watch --mode development --env.configuration=Debug" + }, + "devDependencies": { + "ts-loader": "^6.2.1", + "typescript": "^3.7.5", + "webpack": "^4.41.5", + "webpack-cli": "^3.3.10" + }, + "dependencies": { + "msal": "^1.2.1" + } +} diff --git a/src/Components/WebAssembly/Authentication.Msal/src/Interop/tsconfig.json b/src/Components/WebAssembly/Authentication.Msal/src/Interop/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..13316f2a9e8557aa6d24c87f8ce38ca39a2c5c97 --- /dev/null +++ b/src/Components/WebAssembly/Authentication.Msal/src/Interop/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES2019", + "module": "commonjs", + "lib": [ "DOM", "ES2019" ], + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/src/Components/WebAssembly/Authentication.Msal/src/Interop/webpack.config.js b/src/Components/WebAssembly/Authentication.Msal/src/Interop/webpack.config.js new file mode 100644 index 0000000000000000000000000000000000000000..d3052a790892413e3e4e79305af5238d23ce2beb --- /dev/null +++ b/src/Components/WebAssembly/Authentication.Msal/src/Interop/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); + +module.exports = env => { + + return { + entry: './AuthenticationService.ts', + devtool: env && env.production ? 'none' : 'source-map', + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'], + }, + output: { + filename: 'AuthenticationService.js', + path: path.resolve(__dirname, 'dist', env.configuration), + }, + }; +}; diff --git a/src/Components/WebAssembly/Authentication.Msal/src/Interop/yarn.lock b/src/Components/WebAssembly/Authentication.Msal/src/Interop/yarn.lock new file mode 100644 index 0000000000000000000000000000000000000000..865e68eec452d6984d724e2f078ed085a00cd958 --- /dev/null +++ b/src/Components/WebAssembly/Authentication.Msal/src/Interop/yarn.lock @@ -0,0 +1,2733 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@webassemblyjs/ast@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" + integrity sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ== + dependencies: + "@webassemblyjs/helper-module-context" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/wast-parser" "1.8.5" + +"@webassemblyjs/floating-point-hex-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz#1ba926a2923613edce496fd5b02e8ce8a5f49721" + integrity sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ== + +"@webassemblyjs/helper-api-error@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz#c49dad22f645227c5edb610bdb9697f1aab721f7" + integrity sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA== + +"@webassemblyjs/helper-buffer@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz#fea93e429863dd5e4338555f42292385a653f204" + integrity sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q== + +"@webassemblyjs/helper-code-frame@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz#9a740ff48e3faa3022b1dff54423df9aa293c25e" + integrity sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ== + dependencies: + "@webassemblyjs/wast-printer" "1.8.5" + +"@webassemblyjs/helper-fsm@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz#ba0b7d3b3f7e4733da6059c9332275d860702452" + integrity sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow== + +"@webassemblyjs/helper-module-context@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz#def4b9927b0101dc8cbbd8d1edb5b7b9c82eb245" + integrity sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g== + dependencies: + "@webassemblyjs/ast" "1.8.5" + mamacro "^0.0.3" + +"@webassemblyjs/helper-wasm-bytecode@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz#537a750eddf5c1e932f3744206551c91c1b93e61" + integrity sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ== + +"@webassemblyjs/helper-wasm-section@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz#74ca6a6bcbe19e50a3b6b462847e69503e6bfcbf" + integrity sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + +"@webassemblyjs/ieee754@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz#712329dbef240f36bf57bd2f7b8fb9bf4154421e" + integrity sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.8.5.tgz#044edeb34ea679f3e04cd4fd9824d5e35767ae10" + integrity sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.8.5.tgz#a8bf3b5d8ffe986c7c1e373ccbdc2a0915f0cedc" + integrity sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw== + +"@webassemblyjs/wasm-edit@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz#962da12aa5acc1c131c81c4232991c82ce56e01a" + integrity sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/helper-wasm-section" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + "@webassemblyjs/wasm-opt" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + "@webassemblyjs/wast-printer" "1.8.5" + +"@webassemblyjs/wasm-gen@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz#54840766c2c1002eb64ed1abe720aded714f98bc" + integrity sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/ieee754" "1.8.5" + "@webassemblyjs/leb128" "1.8.5" + "@webassemblyjs/utf8" "1.8.5" + +"@webassemblyjs/wasm-opt@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz#b24d9f6ba50394af1349f510afa8ffcb8a63d264" + integrity sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + +"@webassemblyjs/wasm-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz#21576f0ec88b91427357b8536383668ef7c66b8d" + integrity sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-api-error" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/ieee754" "1.8.5" + "@webassemblyjs/leb128" "1.8.5" + "@webassemblyjs/utf8" "1.8.5" + +"@webassemblyjs/wast-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz#e10eecd542d0e7bd394f6827c49f3df6d4eefb8c" + integrity sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/floating-point-hex-parser" "1.8.5" + "@webassemblyjs/helper-api-error" "1.8.5" + "@webassemblyjs/helper-code-frame" "1.8.5" + "@webassemblyjs/helper-fsm" "1.8.5" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/wast-printer@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz#114bbc481fd10ca0e23b3560fa812748b0bae5bc" + integrity sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/wast-parser" "1.8.5" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +acorn@^6.2.1: + version "6.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.0.tgz#b659d2ffbafa24baf5db1cdbb2c94a983ecd2784" + integrity sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw== + +ajv-errors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" + integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== + +ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" + integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== + +ajv@^6.1.0, ajv@^6.10.2: + version "6.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9" + integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +aproba@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +asn1.js@^4.0.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +assert@^1.1.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== + dependencies: + object-assign "^4.1.1" + util "0.10.3" + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +async-each@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@^1.0.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^1.0.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bluebird@^3.5.5: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: + version "4.11.8" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1, braces@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +brorand@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= + +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= + dependencies: + bn.js "^4.1.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" + integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= + dependencies: + bn.js "^4.1.1" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.2" + elliptic "^6.0.0" + inherits "^2.0.1" + parse-asn1 "^5.0.0" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + dependencies: + pako "~1.0.5" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= + +buffer@^4.3.0: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= + +cacache@^12.0.2: + version "12.0.3" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390" + integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw== + dependencies: + bluebird "^3.5.5" + chownr "^1.1.1" + figgy-pudding "^3.5.1" + glob "^7.1.4" + graceful-fs "^4.1.15" + infer-owner "^1.0.3" + lru-cache "^5.1.1" + mississippi "^3.0.0" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + promise-inflight "^1.0.1" + rimraf "^2.6.3" + ssri "^6.0.1" + unique-filename "^1.1.1" + y18n "^4.0.0" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +chalk@2.4.2, chalk@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chokidar@^2.0.2: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.1" + optionalDependencies: + fsevents "^1.2.7" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chrome-trace-event@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" + integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== + dependencies: + tslib "^1.9.0" + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@^1.5.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +console-browserify@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== + +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= + +copy-concurrently@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" + integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== + dependencies: + aproba "^1.1.1" + fs-write-stream-atomic "^1.0.8" + iferr "^0.1.5" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.0" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +create-ecdh@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" + integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== + dependencies: + bn.js "^4.1.0" + elliptic "^6.0.0" + +create-hash@^1.1.0, create-hash@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cross-spawn@6.0.5, cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +crypto-browserify@^3.11.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + +cyclist@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" + integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= + +debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +des.js@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +detect-file@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" + integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= + +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +domain-browser@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== + +duplexify@^3.4.2, duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +elliptic@^6.0.0: + version "6.5.2" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" + integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emojis-list@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" + integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enhanced-resolve@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" + integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.4.0" + tapable "^1.0.0" + +enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66" + integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + +errno@^0.1.3, errno@~0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" + integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== + dependencies: + prr "~1.0.1" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== + dependencies: + estraverse "^4.1.0" + +estraverse@^4.1.0, estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +events@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" + integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expand-tilde@^2.0.0, expand-tilde@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" + integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= + dependencies: + homedir-polyfill "^1.0.1" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +figgy-pudding@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" + integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-cache-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + dependencies: + commondir "^1.0.1" + make-dir "^2.0.0" + pkg-dir "^3.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +findup-sync@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" + integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + +flush-write-stream@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + dependencies: + inherits "^2.0.3" + readable-stream "^2.3.6" + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +from2@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" + integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" + +fs-write-stream-atomic@^1.0.8: + version "1.0.10" + resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" + integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= + dependencies: + graceful-fs "^4.1.2" + iferr "^0.1.5" + imurmurhash "^0.1.4" + readable-stream "1 || 2" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.2.7: + version "1.2.11" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.11.tgz#67bf57f4758f02ede88fb2a1712fef4d15358be3" + integrity sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob@^7.1.3, glob@^7.1.4: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-modules@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-modules@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" + integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== + dependencies: + global-prefix "^1.0.1" + is-windows "^1.0.1" + resolve-dir "^1.0.0" + +global-prefix@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" + integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= + dependencies: + expand-tilde "^2.0.2" + homedir-polyfill "^1.0.1" + ini "^1.3.4" + is-windows "^1.0.1" + which "^1.2.14" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +hash-base@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hmac-drbg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +homedir-polyfill@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + dependencies: + parse-passwd "^1.0.0" + +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= + +ieee754@^1.1.4: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +iferr@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" + integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= + +import-local@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" + integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== + dependencies: + pkg-dir "^3.0.0" + resolve-cwd "^2.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +infer-owner@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@^1.3.4, ini@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +interpret@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" + integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== + +invert-kv@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" + integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +is-windows@^1.0.1, is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +lcid@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" + integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== + dependencies: + invert-kv "^2.0.0" + +loader-runner@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" + integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== + +loader-utils@1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" + integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== + dependencies: + big.js "^5.2.2" + emojis-list "^2.0.0" + json5 "^1.0.1" + +loader-utils@^1.0.2, loader-utils@^1.2.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +mamacro@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" + integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA== + +map-age-cleaner@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +mem@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" + integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== + dependencies: + map-age-cleaner "^0.1.1" + mimic-fn "^2.0.0" + p-is-promise "^2.0.0" + +memory-fs@^0.4.0, memory-fs@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +micromatch@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +mimic-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +mississippi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" + integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== + dependencies: + concat-stream "^1.5.0" + duplexify "^3.4.2" + end-of-stream "^1.1.0" + flush-write-stream "^1.0.0" + from2 "^2.1.0" + parallel-transform "^1.1.0" + pump "^3.0.0" + pumpify "^1.3.3" + stream-each "^1.1.0" + through2 "^2.0.0" + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +move-concurrently@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" + integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= + dependencies: + aproba "^1.1.1" + copy-concurrently "^1.0.0" + fs-write-stream-atomic "^1.0.8" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.3" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +msal@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/msal/-/msal-1.2.1.tgz#08133e37ab0b9741866c89a3fadc55aadb980723" + integrity sha512-Zo28eyRtT/Un+zcpMfPtTPD+eo/OqzsRER0k5dyk8Mje/K1oLlaEOAgZHlJs59Y2xyuVg8OrcKqSn/1MeNjZYw== + dependencies: + tslib "^1.9.3" + +nan@^2.12.1: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +neo-async@^2.5.0, neo-async@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" + integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-libs-browser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== + dependencies: + assert "^1.1.1" + browserify-zlib "^0.2.0" + buffer "^4.3.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "^3.11.0" + domain-browser "^1.1.1" + events "^3.0.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" + path-browserify "0.0.1" + process "^0.11.10" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.3.3" + stream-browserify "^2.0.1" + stream-http "^2.7.2" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.11.0" + vm-browserify "^1.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= + +os-locale@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" + integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== + dependencies: + execa "^1.0.0" + lcid "^2.0.0" + mem "^4.0.0" + +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-is-promise@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" + integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== + +p-limit@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" + integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== + dependencies: + p-try "^2.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@~1.0.5: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +parallel-transform@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" + integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== + dependencies: + cyclist "^1.0.1" + inherits "^2.0.3" + readable-stream "^2.1.5" + +parse-asn1@^5.0.0: + version "5.1.5" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" + integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== + dependencies: + asn1.js "^4.0.0" + browserify-aes "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +pbkdf2@^3.0.3: + version "3.0.17" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" + integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +picomatch@^2.0.5: + version "2.2.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" + integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.3: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + dependencies: + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + +punycode@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +querystring-es3@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= + dependencies: + resolve-from "^3.0.0" + +resolve-dir@^1.0.0, resolve-dir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" + integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= + dependencies: + expand-tilde "^2.0.0" + global-modules "^1.0.0" + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +rimraf@^2.5.4, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +run-queue@^1.0.0, run-queue@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" + integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= + dependencies: + aproba "^1.1.1" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +schema-utils@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" + integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== + dependencies: + ajv "^6.1.0" + ajv-errors "^1.0.0" + ajv-keywords "^3.1.0" + +semver@^5.5.0, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +serialize-javascript@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" + integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-list-map@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@~0.5.12: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +ssri@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" + integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== + dependencies: + figgy-pudding "^3.5.1" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +stream-browserify@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" + integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + +stream-each@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" + integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== + dependencies: + end-of-stream "^1.1.0" + stream-shift "^1.0.0" + +stream-http@^2.7.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.3.6" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string_decoder@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + +supports-color@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +tapable@^1.0.0, tapable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +terser-webpack-plugin@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" + integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA== + dependencies: + cacache "^12.0.2" + find-cache-dir "^2.1.0" + is-wsl "^1.1.0" + schema-utils "^1.0.0" + serialize-javascript "^2.1.2" + source-map "^0.6.1" + terser "^4.1.2" + webpack-sources "^1.4.0" + worker-farm "^1.7.0" + +terser@^4.1.2: + version "4.6.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.3.tgz#e33aa42461ced5238d352d2df2a67f21921f8d87" + integrity sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + +through2@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +timers-browserify@^2.0.4: + version "2.0.11" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" + integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== + dependencies: + setimmediate "^1.0.4" + +to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +ts-loader@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.1.tgz#67939d5772e8a8c6bdaf6277ca023a4812da02ef" + integrity sha512-Dd9FekWuABGgjE1g0TlQJ+4dFUfYGbYcs52/HQObE0ZmUNjQlmLAS7xXsSzy23AMaMwipsx5sNHvoEpT2CZq1g== + dependencies: + chalk "^2.3.0" + enhanced-resolve "^4.0.0" + loader-utils "^1.0.2" + micromatch "^4.0.0" + semver "^6.0.0" + +tslib@^1.9.0, tslib@^1.9.3: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + +tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +typescript@^3.7.5: + version "3.7.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" + integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= + dependencies: + inherits "2.0.1" + +util@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" + integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== + dependencies: + inherits "2.0.3" + +v8-compile-cache@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" + integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== + +vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== + +watchpack@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" + integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== + dependencies: + chokidar "^2.0.2" + graceful-fs "^4.1.2" + neo-async "^2.5.0" + +webpack-cli@^3.3.10: + version "3.3.11" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.11.tgz#3bf21889bf597b5d82c38f215135a411edfdc631" + integrity sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g== + dependencies: + chalk "2.4.2" + cross-spawn "6.0.5" + enhanced-resolve "4.1.0" + findup-sync "3.0.0" + global-modules "2.0.0" + import-local "2.0.0" + interpret "1.2.0" + loader-utils "1.2.3" + supports-color "6.1.0" + v8-compile-cache "2.0.3" + yargs "13.2.4" + +webpack-sources@^1.4.0, webpack-sources@^1.4.1: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== + dependencies: + source-list-map "^2.0.0" + source-map "~0.6.1" + +webpack@^4.41.5: + version "4.41.6" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.6.tgz#12f2f804bf6542ef166755050d4afbc8f66ba7e1" + integrity sha512-yxXfV0Zv9WMGRD+QexkZzmGIh54bsvEs+9aRWxnN8erLWEOehAKUTeNBoUbA6HPEZPlRo7KDi2ZcNveoZgK9MA== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-module-context" "1.8.5" + "@webassemblyjs/wasm-edit" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + acorn "^6.2.1" + ajv "^6.10.2" + ajv-keywords "^3.4.1" + chrome-trace-event "^1.0.2" + enhanced-resolve "^4.1.0" + eslint-scope "^4.0.3" + json-parse-better-errors "^1.0.2" + loader-runner "^2.4.0" + loader-utils "^1.2.3" + memory-fs "^0.4.1" + micromatch "^3.1.10" + mkdirp "^0.5.1" + neo-async "^2.6.1" + node-libs-browser "^2.2.1" + schema-utils "^1.0.0" + tapable "^1.1.3" + terser-webpack-plugin "^1.4.3" + watchpack "^1.6.0" + webpack-sources "^1.4.1" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@^1.2.14, which@^1.2.9, which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +worker-farm@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" + integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== + dependencies: + errno "~0.1.7" + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +xtend@^4.0.0, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^13.1.0: + version "13.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" + integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@13.2.4: + version "13.2.4" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" + integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + os-locale "^3.1.0" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.0" diff --git a/src/Components/WebAssembly/Authentication.Msal/src/Microsoft.Authentication.WebAssembly.Msal.csproj b/src/Components/WebAssembly/Authentication.Msal/src/Microsoft.Authentication.WebAssembly.Msal.csproj new file mode 100644 index 0000000000000000000000000000000000000000..d2d5b02b4f14d588e32d799368e1dfffe6f8f93e --- /dev/null +++ b/src/Components/WebAssembly/Authentication.Msal/src/Microsoft.Authentication.WebAssembly.Msal.csproj @@ -0,0 +1,83 @@ +<Project Sdk="Microsoft.NET.Sdk.Razor"> + + <Sdk Name="Yarn.MSBuild" /> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + <Description>Authenticate your Blazor webassembly applications with Azure Active Directory and Azure Active Directory B2C</Description> + <IsShippingPackage>true</IsShippingPackage> + <HasReferenceAssembly>false</HasReferenceAssembly> + <RazorLangVersion>3.0</RazorLangVersion> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + </PropertyGroup> + + <ItemGroup> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" /> + </ItemGroup> + + <ItemGroup> + <InternalsVisibleTo Include="Microsoft.Authentication.WebAssembly.Msal.Tests" /> + </ItemGroup> + + <PropertyGroup> + <YarnWorkingDir>$(MSBuildThisFileDirectory)Interop\</YarnWorkingDir> + <ResolveCurrentProjectStaticWebAssetsInputsDependsOn> + CompileInterop; + $(ResolveCurrentProjectStaticWebAssetsInputsDependsOn) + </ResolveCurrentProjectStaticWebAssetsInputsDependsOn> + </PropertyGroup> + + <ItemGroup> + <YarnInputs Include="$(YarnWorkingDir)**" Exclude="$(YarnWorkingDir)node_modules\**;$(YarnWorkingDir)*.d.ts;$(YarnWorkingDir)dist\**" /> + <YarnOutputs Include="$(YarnWorkingDir)dist\$(Configuration)\AuthenticationService.js" /> + <YarnOutputs Include="$(YarnWorkingDir)dist\$(Configuration)\AuthenticationService.js.map" Condition="'$(Configuration)' == 'Debug'" /> + + <Content Remove="$(YarnWorkingDir)**" /> + <None Include="$(YarnWorkingDir)*" Exclude="$(YarnWorkingDir)node_modules\**" /> + + <UpToDateCheckInput Include="@(YarnInputs)" Set="StaticWebassets" /> + <UpToDateCheckOutput Include="@(YarnOutputs)" Set="StaticWebassets" /> + </ItemGroup> + + <Target Name="_CreateInteropHash" BeforeTargets="CompileInterop" Condition="'$(DesignTimeBuild)' != 'true'"> + + <PropertyGroup> + <InteropCompilationCacheFile>$(IntermediateOutputPath)interop.cache</InteropCompilationCacheFile> + </PropertyGroup> + + <Hash ItemsToHash="@(YarnInputs)"> + <Output TaskParameter="HashResult" PropertyName="_YarnInputsHash" /> + </Hash> + + <WriteLinesToFile Lines="$(_YarnInputsHash)" File="$(InteropCompilationCacheFile)" Overwrite="True" WriteOnlyWhenDifferent="True" /> + + <ItemGroup> + <FileWrites Include="$(InteropCompilationCacheFile)" /> + </ItemGroup> + + </Target> + + <Target Name="CompileInterop" Condition="'$(DesignTimeBuild)' != 'true'" Inputs="$(InteropCompilationCacheFile)" Outputs="@(YarnOutputs)"> + <Yarn Command="install --mutex network" WorkingDirectory="$(YarnWorkingDir)" /> + <Yarn Command="run build:release" WorkingDirectory="$(YarnWorkingDir)" Condition="'$(Configuration)' == 'Release'" /> + <Yarn Command="run build:debug" WorkingDirectory="$(YarnWorkingDir)" Condition="'$(Configuration)' == 'Debug'" /> + + <ItemGroup> + <_InteropBuildOutput Include="$(YarnWorkingDir)dist\$(Configuration)\**" Exclude="$(YarnWorkingDir)dist\.gitignore" /> + + <StaticWebAsset Include="@(_InteropBuildOutput->'%(FullPath)')"> + <SourceType></SourceType> + <SourceId>$(PackageId)</SourceId> + <ContentRoot>$([MSBuild]::NormalizeDirectory('$(YarnWorkingDir)\dist\$(Configuration)'))</ContentRoot> + <BasePath>_content/$(PackageId)</BasePath> + <RelativePath>$([System.String]::Copy('%(RecursiveDir)%(FileName)%(Extension)').Replace('\','/'))</RelativePath> + </StaticWebAsset> + + <FileWrites Include="$(_InteropBuildOutput)" /> + </ItemGroup> + + <Message Importance="high" Text="@(_InteropBuildOutput->'Emitted %(FullPath)')" /> + + </Target> + +</Project> diff --git a/src/Components/WebAssembly/Authentication.Msal/src/Models/MsalAuthenticationOptions.cs b/src/Components/WebAssembly/Authentication.Msal/src/Models/MsalAuthenticationOptions.cs new file mode 100644 index 0000000000000000000000000000000000000000..fb75968b66101bc6a00d5bc944b6a6d07f6319ec --- /dev/null +++ b/src/Components/WebAssembly/Authentication.Msal/src/Models/MsalAuthenticationOptions.cs @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Authentication.WebAssembly.Msal +{ + /// <summary> + /// Authentication options for the underlying msal.js library handling the authentication. + /// </summary> + public class MsalAuthenticationOptions + { + /// <summary> + /// Gets or sets the client id for the application. + /// </summary> + public string ClientId { get; set; } + + /// <summary> + /// Gets or sets the authority for the Azure Active Directory or Azure Active Directory B2C instance. + /// </summary> + public string Authority { get; set; } + + /// <summary> + /// Gets or sets a value that indicates whether or not to validate the authority. + /// </summary> + /// <remarks> + /// This value needs to be set to false when using Azure Active Directory B2C. + /// </remarks> + public bool ValidateAuthority { get; set; } = true; + + /// <summary> + /// Gets or sets the redirect uri for the application. + /// </summary> + /// <remarks> + /// It can be an absolute or base relative <see cref="Uri"/> and defaults to <c>authentication/login-callback.</c> + /// </remarks> + public string RedirectUri { get; set; } + + /// <summary> + /// Gets or sets the post logout redirect uri for the application. + /// </summary> + /// <remarks> + /// It can be an absolute or base relative <see cref="Uri"/> and defaults to <c>authentication/logout-callback.</c> + /// </remarks> + public string PostLogoutRedirectUri { get; set; } + + /// <summary> + /// Gets or sets whether or not to navigate to the login request url after a successful login. + /// </summary> + public bool NavigateToLoginRequestUrl { get; set; } = false; + } +} diff --git a/src/Components/WebAssembly/Authentication.Msal/src/Models/MsalCacheOptions.cs b/src/Components/WebAssembly/Authentication.Msal/src/Models/MsalCacheOptions.cs new file mode 100644 index 0000000000000000000000000000000000000000..961b8ecea654c84700cc082a15718776c029d35b --- /dev/null +++ b/src/Components/WebAssembly/Authentication.Msal/src/Models/MsalCacheOptions.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.Authentication.WebAssembly.Msal.Models +{ + /// <summary> + /// Cache options for the msal.js cache. + /// </summary> + public class MsalCacheOptions + { + /// <summary> + /// Gets or sets the cache location. + /// </summary> + /// <remarks> + /// Valid values are <c>sessionStorage</c> and <c>localStorage</c>. + /// </remarks> + public string CacheLocation { get; set; } + + /// <summary> + /// Gets or sets whether to store the authentication state in a cookie. + /// </summary> + public bool StoreAuthStateInCookie { get; set; } + } +} diff --git a/src/Components/WebAssembly/Authentication.Msal/src/Models/MsalProviderOptions.cs b/src/Components/WebAssembly/Authentication.Msal/src/Models/MsalProviderOptions.cs new file mode 100644 index 0000000000000000000000000000000000000000..8fa6b8b29249d844c53a135dd9b41902232b7700 --- /dev/null +++ b/src/Components/WebAssembly/Authentication.Msal/src/Models/MsalProviderOptions.cs @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Microsoft.Authentication.WebAssembly.Msal.Models +{ + /// <summary> + /// Authentication provider options for the msal.js authentication provider. + /// </summary> + public class MsalProviderOptions + { + /// <summary> + /// Gets or sets the <see cref="MsalAuthenticationOptions"/> to use for authentication operations. + /// </summary> + [JsonPropertyName("auth")] + public MsalAuthenticationOptions Authentication { get; set; } = new MsalAuthenticationOptions + { + RedirectUri = "authentication/login-callback", + PostLogoutRedirectUri = "authentication/logout-callback" + }; + + /// <summary> + /// Gets or sets the msal.js cache options. + /// </summary> + public MsalCacheOptions Cache { get; set; } = new MsalCacheOptions + { + // This matches the defaults in msal.js + CacheLocation = "sessionStorage", + StoreAuthStateInCookie = false + }; + + /// <summary> + /// Gets or set the list of default access tokens scopes to provision during the sign-in flow. + /// </summary> + public IList<string> DefaultAccessTokenScopes { get; set; } = new List<string>(); + + /// <summary> + /// Gets or sets a list of additional scopes to consent during the initial sign-in flow. + /// </summary> + /// <remarks> + /// Use this parameter to request consent for scopes for other resources. + /// </remarks> + public IList<string> AdditionalScopesToConsent { get; set; } = new List<string>(); + } +} diff --git a/src/Components/WebAssembly/Authentication.Msal/src/MsalDefaultOptionsConfiguration.cs b/src/Components/WebAssembly/Authentication.Msal/src/MsalDefaultOptionsConfiguration.cs new file mode 100644 index 0000000000000000000000000000000000000000..c98c6f871e825f874868145c19064a7d677a60da --- /dev/null +++ b/src/Components/WebAssembly/Authentication.Msal/src/MsalDefaultOptionsConfiguration.cs @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +using Microsoft.Authentication.WebAssembly.Msal.Models; +using Microsoft.Extensions.Options; + +namespace Microsoft.Authentication.WebAssembly.Msal +{ + internal class MsalDefaultOptionsConfiguration : IPostConfigureOptions<RemoteAuthenticationOptions<MsalProviderOptions>> + { + private readonly NavigationManager _navigationManager; + + public MsalDefaultOptionsConfiguration(NavigationManager navigationManager) + { + _navigationManager = navigationManager; + } + + public void Configure(RemoteAuthenticationOptions<MsalProviderOptions> options) + { + options.UserOptions.ScopeClaim ??= "scp"; + options.UserOptions.AuthenticationType ??= options.ProviderOptions.Authentication.ClientId; + + var redirectUri = options.ProviderOptions.Authentication.RedirectUri; + if (redirectUri == null || !Uri.TryCreate(redirectUri, UriKind.Absolute, out _)) + { + redirectUri ??= "authentication/login-callback"; + options.ProviderOptions.Authentication.RedirectUri = _navigationManager + .ToAbsoluteUri(redirectUri).AbsoluteUri; + } + + var logoutUri = options.ProviderOptions.Authentication.PostLogoutRedirectUri; + if (logoutUri == null || !Uri.TryCreate(logoutUri, UriKind.Absolute, out _)) + { + logoutUri ??= "authentication/logout-callback"; + options.ProviderOptions.Authentication.PostLogoutRedirectUri = _navigationManager + .ToAbsoluteUri(logoutUri).AbsoluteUri; + } + + options.ProviderOptions.Authentication.NavigateToLoginRequestUrl = false; + } + + public void PostConfigure(string name, RemoteAuthenticationOptions<MsalProviderOptions> options) + { + if (string.Equals(name, Options.DefaultName)) + { + Configure(options); + } + } + } +} diff --git a/src/Components/WebAssembly/Authentication.Msal/src/MsalWebAssemblyServiceCollectionExtensions.cs b/src/Components/WebAssembly/Authentication.Msal/src/MsalWebAssemblyServiceCollectionExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..9320f7c74832d85a3d7d23f11aaed9c7b86d18de --- /dev/null +++ b/src/Components/WebAssembly/Authentication.Msal/src/MsalWebAssemblyServiceCollectionExtensions.cs @@ -0,0 +1,71 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +using Microsoft.Authentication.WebAssembly.Msal; +using Microsoft.Authentication.WebAssembly.Msal.Models; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// <summary> + /// Contains extension methods to add authentication to Blazor WebAssembly applications using + /// Azure Active Directory or Azure Active Directory B2C. + /// </summary> + public static class MsalWebAssemblyServiceCollectionExtensions + { + /// <summary> + /// Adds authentication using msal.js to Blazor applications. + /// </summary> + /// <param name="services">The <see cref="IServiceCollection"/>.</param> + /// <param name="configure">A callback to configure the <see cref="RemoteAuthenticationOptions{MsalProviderOptions}"/>.</param> + /// <returns>The <see cref="IServiceCollection"/>.</returns> + public static IRemoteAuthenticationBuilder<RemoteAuthenticationState, RemoteUserAccount> AddMsalAuthentication(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure) + { + return AddMsalAuthentication<RemoteAuthenticationState>(services, configure); + } + + /// <summary> + /// Adds authentication using msal.js to Blazor applications. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam> + /// <param name="services">The <see cref="IServiceCollection"/>.</param> + /// <param name="configure">A callback to configure the <see cref="RemoteAuthenticationOptions{MsalProviderOptions}"/>.</param> + /// <returns>The <see cref="IServiceCollection"/>.</returns> + public static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, RemoteUserAccount> AddMsalAuthentication<TRemoteAuthenticationState>(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure) + where TRemoteAuthenticationState : RemoteAuthenticationState, new() + { + return AddMsalAuthentication<TRemoteAuthenticationState, RemoteUserAccount>(services, configure); + } + + /// <summary> + /// Adds authentication using msal.js to Blazor applications. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam> + /// <typeparam name="TAccount">The type of the <see cref="RemoteUserAccount"/>.</typeparam> + /// <param name="services">The <see cref="IServiceCollection"/>.</param> + /// <param name="configure">A callback to configure the <see cref="RemoteAuthenticationOptions{MsalProviderOptions}"/>.</param> + /// <returns>The <see cref="IServiceCollection"/>.</returns> + public static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount> AddMsalAuthentication<TRemoteAuthenticationState, TAccount>(this IServiceCollection services, Action<RemoteAuthenticationOptions<MsalProviderOptions>> configure) + where TRemoteAuthenticationState : RemoteAuthenticationState, new() + where TAccount : RemoteUserAccount + { + services.AddRemoteAuthentication<TRemoteAuthenticationState, TAccount, MsalProviderOptions>(configure); + services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<MsalProviderOptions>>, MsalDefaultOptionsConfiguration>()); + + return new MsalRemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount>(services); + } + } + + internal class MsalRemoteAuthenticationBuilder<TRemoteAuthenticationState, TRemoteUserAccount> : IRemoteAuthenticationBuilder<TRemoteAuthenticationState, TRemoteUserAccount> + where TRemoteAuthenticationState : RemoteAuthenticationState, new() + where TRemoteUserAccount : RemoteUserAccount + { + + public MsalRemoteAuthenticationBuilder(IServiceCollection services) => Services = services; + + public IServiceCollection Services { get; } + } +} diff --git a/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj b/src/Components/WebAssembly/Build/src/Microsoft.AspNetCore.Components.WebAssembly.Build.csproj similarity index 63% rename from src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj rename to src/Components/WebAssembly/Build/src/Microsoft.AspNetCore.Components.WebAssembly.Build.csproj index 8262e1139de9e107a94a7b6778b3fa1cf1b90dba..796ee553e361d83843eb8865f0ba3c1165462204 100644 --- a/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.csproj +++ b/src/Components/WebAssembly/Build/src/Microsoft.AspNetCore.Components.WebAssembly.Build.csproj @@ -1,11 +1,11 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFrameworks>$(DefaultNetCoreTargetFramework);net46</TargetFrameworks> - <TargetName>Microsoft.AspNetCore.Blazor.Build.Tasks</TargetName> - <AssemblyName>Microsoft.AspNetCore.Blazor.Build</AssemblyName> - <Description>Build mechanism for ASP.NET Core Blazor applications.</Description> - <IsShippingPackage>false</IsShippingPackage> + <TargetName>Microsoft.AspNetCore.Components.WebAssembly.Build.Tasks</TargetName> + <AssemblyName>Microsoft.AspNetCore.Components.WebAssembly.Build</AssemblyName> + <Description>Build mechanism for ASP.NET Core Blazor WebAssembly applications.</Description> + <IsShippingPackage>true</IsShippingPackage> <HasReferenceAssembly>false</HasReferenceAssembly> <GenerateDependencyFile>false</GenerateDependencyFile> </PropertyGroup> @@ -15,18 +15,20 @@ <!-- Producing this package requires building with NodeJS enabled. --> <IsPackable Condition="'$(BuildNodeJS)' == 'false'">false</IsPackable> <NoPackageAnalysis>true</NoPackageAnalysis> - <NuspecFile>Microsoft.AspNetCore.Blazor.Build.nuspec</NuspecFile> + <NuspecFile>Microsoft.AspNetCore.Components.WebAssembly.Build.nuspec</NuspecFile> </PropertyGroup> <ItemGroup> <NuspecProperty Include="configuration=$(Configuration)" /> <NuspecProperty Include="taskskDir=$(OutputPath)tools" /> - <NuspecProperty Include="componentsversion=$(ComponentsPackageVersion)" /> - <NuspecProperty Include="razorversion=$(MicrosoftAspNetCoreRazorDesignPackageVersion)" /> - <NuspecProperty Include="blazormonoversion=$(MicrosoftAspNetCoreBlazorMonoPackageVersion)" /> + <NuspecProperty Include="MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion=$(MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion)" /> <NuspecProperty Include="PackageThirdPartyNoticesFile=$(PackageThirdPartyNoticesFile)" /> </ItemGroup> + <ItemGroup> + <InternalsVisibleTo Include="Microsoft.AspNetCore.Components.WebAssembly.Build.Tests" /> + </ItemGroup> + <ItemGroup> <!-- Add a project dependency without reference output assemblies to enforce build order --> <!-- Applying workaround for https://github.com/microsoft/msbuild/issues/2661 and https://github.com/dotnet/sdk/issues/952 --> @@ -55,11 +57,11 @@ you will need to manaully stop MSBuild.exe processes --> - <ItemGroup> + <ItemGroup> <_NetCoreFilesToCopy Include="$(OutputPath)$(DefaultNetCoreTargetFramework)\*" TargetPath="netcoreapp\" /> <_DesktopFilesToCopy Include="$(OutputPath)net46\*" TargetPath="netfx\" /> <_AllFilesToCopy Include="@(_NetCoreFilesToCopy);@(_DesktopFilesToCopy)" /> - </ItemGroup> + </ItemGroup> <Error Text="No files found in $(OutputPath)$(DefaultNetCoreTargetFramework)" Condition="@(_NetCoreFilesToCopy->Count()) == 0" /> <Error Text="No files found in $(OutputPath)net46" Condition="@(_DesktopFilesToCopy->Count()) == 0" /> @@ -69,4 +71,36 @@ </Copy> </Target> + <ItemGroup> + <_BrotliToolPathInput Include="..\..\Compression\src\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj" /> + <_BrotliToolPathInput Include="..\..\Compression\src\*.cs" /> + <_BrotliToolPathInput Include="..\..\Compression\src\*.runtimeconfig.json" /> + <_BrotliToolPathOutput Include="$(MSBuildThisFileDirectory)bin\$(Configuration)\tools\compression\blazor-brotli.dll" /> + </ItemGroup> + + <Target + Name="GetBrotliTools" + BeforeTargets="Build;GenerateNuspec" + Inputs="@(_BrotliToolPathInput)" + Outputs="@(_BrotliToolPathOutput)"> + <ItemGroup> + <_BrotliToolsPath Include="$(MSBuildThisFileDirectory)bin\$(Configuration)\tools\compression\" /> + </ItemGroup> + + <PropertyGroup> + <_BrotliToolsOutputPath>@(_BrotliToolsPath->'%(FullPath)')</_BrotliToolsOutputPath> + </PropertyGroup> + + <ItemGroup> + <NuspecProperty Include="brotliDir=$(_BrotliToolsOutputPath)" /> + </ItemGroup> + + <MSBuild + Projects="..\..\Compression\src\Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj" + Targets="Publish" + Properties="Configuration=$(Configuration);TargetFramework=netcoreapp3.1;PublishDir=$(_BrotliToolsOutputPath)"> + </MSBuild> + + </Target> + </Project> diff --git a/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.nuspec b/src/Components/WebAssembly/Build/src/Microsoft.AspNetCore.Components.WebAssembly.Build.nuspec similarity index 64% rename from src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.nuspec rename to src/Components/WebAssembly/Build/src/Microsoft.AspNetCore.Components.WebAssembly.Build.nuspec index 459fed97d0a7a429aa0d5f841d44699552889970..9b8aa9ad3a19d8c09cc3fc4dd0baf37b3813e905 100644 --- a/src/Components/Blazor/Build/src/Microsoft.AspNetCore.Blazor.Build.nuspec +++ b/src/Components/WebAssembly/Build/src/Microsoft.AspNetCore.Components.WebAssembly.Build.nuspec @@ -3,13 +3,15 @@ <metadata> $CommonMetadataElements$ <dependencies> - <dependency id="Microsoft.AspNetCore.Blazor.Mono" version="$blazormonoversion$" include="all" /> + <dependency id="Microsoft.AspNetCore.Components.WebAssembly.Runtime" version="$MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion$" include="all" /> </dependencies> </metadata> <files> $CommonFileElements$ <file src="$PackageThirdPartyNoticesFile$" target=".\THIRD-PARTY-NOTICES.txt" /> <file src="build\**" target="build" /> + <file src="$brotliDir$blazor-brotli.dll" target="tools/compression" /> + <file src="$brotliDir$blazor-brotli.runtimeconfig.json" target="tools/compression" /> <file src="targets\**" target="targets" /> <file src="$taskskDir$\**" target="tools/" /> <file src="..\..\..\Web.JS\dist\$configuration$\blazor.webassembly.js" target="tools/blazor" /> diff --git a/src/Components/Blazor/Build/src/ReferenceBlazorBuildFromSource.props b/src/Components/WebAssembly/Build/src/ReferenceBlazorBuildFromSource.props similarity index 56% rename from src/Components/Blazor/Build/src/ReferenceBlazorBuildFromSource.props rename to src/Components/WebAssembly/Build/src/ReferenceBlazorBuildFromSource.props index 0bcebe22fa129877d6ea9a464f6e59495244fe86..d480df7f9541251b8c8ddd456324fa3e2d8d6c0e 100644 --- a/src/Components/Blazor/Build/src/ReferenceBlazorBuildFromSource.props +++ b/src/Components/WebAssembly/Build/src/ReferenceBlazorBuildFromSource.props @@ -10,13 +10,15 @@ <PropertyGroup> <ComponentsRoot Condition="'$(ComponentsRoot)'==''">$(MSBuildThisFileDirectory)..\..\..\</ComponentsRoot> - <BlazorJsPath>$(ComponentsRoot)Web.JS\dist\$(Configuration)\blazor.webassembly.js</BlazorJsPath> - <BlazorJsMapPath>$(ComponentsRoot)Web.JS\dist\$(Configuration)\blazor.webassembly.js.map</BlazorJsMapPath> - <BlazorToolsDir>$(MSBuildThisFileDirectory)bin\$(Configuration)\tools\</BlazorToolsDir> + <BlazorBuildConfiguration Condition="'$(BlazorBuildConfiguration)'==''">$(Configuration)</BlazorBuildConfiguration> + + <_BlazorJsPath>$(ComponentsRoot)Web.JS\dist\$(BlazorBuildConfiguration)\blazor.webassembly.js</_BlazorJsPath> + <_BlazorJsMapPath>$(ComponentsRoot)Web.JS\dist\$(BlazorBuildConfiguration)\blazor.webassembly.js.map</_BlazorJsMapPath> + <_BlazorToolsDir>$(MSBuildThisFileDirectory)bin\$(BlazorBuildConfiguration)\tools\</_BlazorToolsDir> </PropertyGroup> - <Target Name="CheckBlazorJSFiles" BeforeTargets="Build"> - <Error Text="blazor.webassembly.js file could not be found at $(BlazorJsPath)" Condition="!Exists($(BlazorJsPath))" /> + <Target Name="Check_BlazorJSFiles" BeforeTargets="Build"> + <Error Text="blazor.webassembly.js file could not be found at $(_BlazorJsPath)" Condition="!Exists($(_BlazorJsPath))" /> </Target> <Import Project="$(MSBuildThisFileDirectory)targets/All.props" /> diff --git a/src/Components/Blazor/Build/src/ReferenceFromSource.props b/src/Components/WebAssembly/Build/src/ReferenceFromSource.props similarity index 90% rename from src/Components/Blazor/Build/src/ReferenceFromSource.props rename to src/Components/WebAssembly/Build/src/ReferenceFromSource.props index 37e2b60e16f06eb3315af13c6263c255467c390f..afa2a5ada1877892d90b504fa55c590eb7947983 100644 --- a/src/Components/Blazor/Build/src/ReferenceFromSource.props +++ b/src/Components/WebAssembly/Build/src/ReferenceFromSource.props @@ -17,7 +17,7 @@ </PropertyGroup> <ItemGroup> - <Reference Include="Microsoft.AspNetCore.Blazor.Mono" /> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly.Runtime" /> </ItemGroup> <Target Name="_BuildBlazorBuildProject" BeforeTargets="ResolveProjectReferences"> @@ -25,7 +25,7 @@ The Blazor.Build project cross-compiles and we need the output of all TFMs to be available in the build output. We can't represent this in any good way using ProjectReference elements, but we can try and build it instead. --> - <MSBuild Projects="$(MSBuildThisFileDirectory)Microsoft.AspNetCore.Blazor.Build.csproj" /> + <MSBuild Projects="$(MSBuildThisFileDirectory)Microsoft.AspNetCore.Components.WebAssembly.Build.csproj" /> </Target> <!-- This is used as a P2P when building the repo. Normal Blazor projects will get this as a reference through the Blazor.Build package --> @@ -33,12 +33,12 @@ <!-- Ensures these projects are built before the consuming project, but without adding a runtime dependency on the .dll (to be equivalent to a <PackageDependency> given that the packed version of this project wouldn't add a .dll reference) --> - <ProjectReference Include="$(MSBuildThisFileDirectory)Microsoft.AspNetCore.Blazor.Build.csproj"> + <ProjectReference Include="$(MSBuildThisFileDirectory)Microsoft.AspNetCore.Components.WebAssembly.Build.csproj"> <ReferenceOutputAssembly>false</ReferenceOutputAssembly> <SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties> <Properties>TargetFramework=$(DefaultNetCoreTargetFramework)</Properties> </ProjectReference> - <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj"> + <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\DevServer\src\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj"> <ReferenceOutputAssembly>false</ReferenceOutputAssembly> <!-- Optimization. Do not require framework compatibility between these projects. --> <SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties> diff --git a/src/Components/Blazor/Build/src/Tasks/BlazorCreateRootDescriptorFile.cs b/src/Components/WebAssembly/Build/src/Tasks/BlazorCreateRootDescriptorFile.cs similarity index 96% rename from src/Components/Blazor/Build/src/Tasks/BlazorCreateRootDescriptorFile.cs rename to src/Components/WebAssembly/Build/src/Tasks/BlazorCreateRootDescriptorFile.cs index 1aa853685671a61f982f7f386ea69f1b5d2bc5ce..45359bdb8ad6eb4b47e937d62aabe91af6c58f5b 100644 --- a/src/Components/Blazor/Build/src/Tasks/BlazorCreateRootDescriptorFile.cs +++ b/src/Components/WebAssembly/Build/src/Tasks/BlazorCreateRootDescriptorFile.cs @@ -9,7 +9,7 @@ using System.Xml.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Microsoft.AspNetCore.Blazor.Build +namespace Microsoft.AspNetCore.Components.WebAssembly.Build { // Based on https://github.com/mono/linker/blob/3b329b9481e300bcf4fb88a2eebf8cb5ef8b323b/src/ILLink.Tasks/CreateRootDescriptorFile.cs public class BlazorCreateRootDescriptorFile : Task diff --git a/src/Components/WebAssembly/Build/src/Tasks/BlazorGetFileHash.cs b/src/Components/WebAssembly/Build/src/Tasks/BlazorGetFileHash.cs new file mode 100644 index 0000000000000000000000000000000000000000..f10f53a7483ad8101818b5dc725b34716c1b2cf7 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/Tasks/BlazorGetFileHash.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.Build.Tasks +{ + /// <summary> + /// Computes the checksum for a single file. + /// </summary> + public sealed class BlazorGetFileHash : Task + { + internal const string _defaultFileHashAlgorithm = "SHA256"; + internal const string _hashEncodingBase64 = "base64"; + + /// <summary> + /// The files to be hashed. + /// </summary> + [Required] + public ITaskItem[] Files { get; set; } + + /// <summary> + /// The algorithm. Allowed values: SHA256, SHA384, SHA512. Default = SHA256. + /// </summary> + public string Algorithm { get; set; } = _defaultFileHashAlgorithm; + + /// <summary> + /// The metadata name where the hash is stored in each item. Defaults to "FileHash". + /// </summary> + public string MetadataName { get; set; } = "FileHash"; + + /// <summary> + /// The encoding to use for generated hashs. Defaults to "hex". Allowed values = "base64". + /// </summary> + public string HashEncoding { get; set; } = _hashEncodingBase64; + + /// <summary> + /// The hash of the file. This is only set if there was one item group passed in. + /// </summary> + [Output] + public string Hash { get; set; } + + /// <summary> + /// The input files with additional metadata set to include the file hash. + /// </summary> + [Output] + public ITaskItem[] Items { get; set; } + + public override bool Execute() + { + if (!SupportsAlgorithm(Algorithm)) + { + Log.LogError("Unrecognized HashAlgorithm {0}", Algorithm); + return false; + } + + System.Threading.Tasks.Parallel.ForEach(Files, file => + { + if (!File.Exists(file.ItemSpec)) + { + Log.LogError("File not found '{0}", file.ItemSpec); + } + }); + + if (Log.HasLoggedErrors) + { + return false; + } + + System.Threading.Tasks.Parallel.ForEach(Files, file => + { + var hash = ComputeHash(Algorithm, file.ItemSpec); + file.SetMetadata("FileHashAlgorithm", Algorithm); + file.SetMetadata(MetadataName, EncodeHash(hash)); + }); + + Items = Files; + + if (Files.Length == 1) + { + Hash = Files[0].GetMetadata(MetadataName); + } + + return !Log.HasLoggedErrors; + } + + internal static string EncodeHash(byte[] hash) + { + return Convert.ToBase64String(hash); + } + + internal static bool SupportsAlgorithm(string algorithmName) + => _supportedAlgorithms.Contains(algorithmName); + + internal static byte[] ComputeHash(string algorithmName, string filePath) + { + using (var stream = File.OpenRead(filePath)) + using (var algorithm = CreateAlgorithm(algorithmName)) + { + return algorithm.ComputeHash(stream); + } + } + + private static readonly HashSet<string> _supportedAlgorithms + = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + { + "SHA256", + "SHA384", + "SHA512", + }; + + private static HashAlgorithm CreateAlgorithm(string algorithmName) + { + if (string.Equals(algorithmName, "SHA256", StringComparison.OrdinalIgnoreCase)) + { + return SHA256.Create(); + } + else if (string.Equals(algorithmName, "SHA384", StringComparison.OrdinalIgnoreCase)) + { + return SHA384.Create(); + } + else if (string.Equals(algorithmName, "SHA512", StringComparison.OrdinalIgnoreCase)) + { + return SHA512.Create(); + } + + throw new ArgumentOutOfRangeException(); + } + } +} \ No newline at end of file diff --git a/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs b/src/Components/WebAssembly/Build/src/Tasks/BlazorILLink.cs similarity index 99% rename from src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs rename to src/Components/WebAssembly/Build/src/Tasks/BlazorILLink.cs index d5dc22cde02c29bc424d41abe8319e7bb40a80bd..0244e9f0c2c6c50c66edde826a626a2c0a5e1d85 100644 --- a/src/Components/Blazor/Build/src/Tasks/BlazorILLink.cs +++ b/src/Components/WebAssembly/Build/src/Tasks/BlazorILLink.cs @@ -8,7 +8,7 @@ using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Microsoft.AspNetCore.Blazor.Build.Tasks +namespace Microsoft.AspNetCore.Components.WebAssembly.Build.Tasks { // Based on https://github.com/mono/linker/blob/3b329b9481e300bcf4fb88a2eebf8cb5ef8b323b/src/ILLink.Tasks/LinkTask.cs public class BlazorILLink : ToolTask diff --git a/src/Components/WebAssembly/Build/src/Tasks/BlazorReadSatelliteAssemblyFile.cs b/src/Components/WebAssembly/Build/src/Tasks/BlazorReadSatelliteAssemblyFile.cs new file mode 100644 index 0000000000000000000000000000000000000000..18a3fc8214b21c5584b13591c1d118d4fcf1dd1e --- /dev/null +++ b/src/Components/WebAssembly/Build/src/Tasks/BlazorReadSatelliteAssemblyFile.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using System.Xml.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build +{ + public class BlazorReadSatelliteAssemblyFile : Task + { + [Output] + public ITaskItem[] SatelliteAssembly { get; set; } + + [Required] + public ITaskItem ReadFile { get; set; } + + public override bool Execute() + { + var document = XDocument.Load(ReadFile.ItemSpec); + SatelliteAssembly = document.Root + .Elements() + .Select(e => + { + // <Assembly Name="..." Culture="..." DestinationSubDirectory="..." /> + + var taskItem = new TaskItem(e.Attribute("Name").Value); + taskItem.SetMetadata("Culture", e.Attribute("Culture").Value); + taskItem.SetMetadata("DestinationSubDirectory", e.Attribute("DestinationSubDirectory").Value); + + return taskItem; + }).ToArray(); + + return true; + } + } +} diff --git a/src/Components/WebAssembly/Build/src/Tasks/BlazorWriteSatelliteAssemblyFile.cs b/src/Components/WebAssembly/Build/src/Tasks/BlazorWriteSatelliteAssemblyFile.cs new file mode 100644 index 0000000000000000000000000000000000000000..e1e6a8150ea032a439a9697e7cc2b96a4896d9c1 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/Tasks/BlazorWriteSatelliteAssemblyFile.cs @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Xml; +using System.Xml.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build +{ + public class BlazorWriteSatelliteAssemblyFile : Task + { + [Required] + public ITaskItem[] SatelliteAssembly { get; set; } + + [Required] + public ITaskItem WriteFile { get; set; } + + public override bool Execute() + { + using var fileStream = File.Create(WriteFile.ItemSpec); + WriteSatelliteAssemblyFile(fileStream); + return true; + } + + internal void WriteSatelliteAssemblyFile(Stream stream) + { + var root = new XElement("SatelliteAssembly"); + + foreach (var item in SatelliteAssembly) + { + // <Assembly Name="..." Culture="..." DestinationSubDirectory="..." /> + + root.Add(new XElement("Assembly", + new XAttribute("Name", item.ItemSpec), + new XAttribute("Culture", item.GetMetadata("Culture")), + new XAttribute("DestinationSubDirectory", item.GetMetadata("DestinationSubDirectory")))); + } + + var xmlWriterSettings = new XmlWriterSettings + { + Indent = true, + OmitXmlDeclaration = true + }; + + using var writer = XmlWriter.Create(stream, xmlWriterSettings); + var xDocument = new XDocument(root); + + xDocument.Save(writer); + } + } +} diff --git a/src/Components/WebAssembly/Build/src/Tasks/BrotliCompressBlazorApplicationFiles.cs b/src/Components/WebAssembly/Build/src/Tasks/BrotliCompressBlazorApplicationFiles.cs new file mode 100644 index 0000000000000000000000000000000000000000..fc4a4e6ad9d0011610b6e02a417d9cd79c86de73 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/Tasks/BrotliCompressBlazorApplicationFiles.cs @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build +{ + public class BrotliCompressBlazorApplicationFiles : ToolTask + { + private const string DotNetHostPathEnvironmentName = "DOTNET_HOST_PATH"; + + [Required] + public string ManifestPath { get; set; } + + [Required] + public string BlazorBrotliPath { get; set; } + + private string _dotnetPath; + + private string DotNetPath + { + get + { + if (!string.IsNullOrEmpty(_dotnetPath)) + { + return _dotnetPath; + } + + _dotnetPath = Environment.GetEnvironmentVariable(DotNetHostPathEnvironmentName); + if (string.IsNullOrEmpty(_dotnetPath)) + { + throw new InvalidOperationException($"{DotNetHostPathEnvironmentName} is not set"); + } + + return _dotnetPath; + } + } + + protected override MessageImportance StandardErrorLoggingImportance => MessageImportance.High; + + protected override string ToolName => Path.GetFileName(DotNetPath); + + protected override string GenerateFullPathToTool() => DotNetPath; + + protected override string GenerateCommandLineCommands() => + $"\"{BlazorBrotliPath}\" \"{ManifestPath}\""; + } +} diff --git a/src/Components/WebAssembly/Build/src/Tasks/GenerateBlazorBootJson.cs b/src/Components/WebAssembly/Build/src/Tasks/GenerateBlazorBootJson.cs new file mode 100644 index 0000000000000000000000000000000000000000..047f08d5dd9a4e51dc09f74e84655269086ccb6c --- /dev/null +++ b/src/Components/WebAssembly/Build/src/Tasks/GenerateBlazorBootJson.cs @@ -0,0 +1,224 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using ResourceHashesByNameDictionary = System.Collections.Generic.Dictionary<string, string>; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build +{ + public class GenerateBlazorBootJson : Task + { + [Required] + public string AssemblyPath { get; set; } + + [Required] + public ITaskItem[] Resources { get; set; } + + [Required] + public bool DebugBuild { get; set; } + + [Required] + public bool LinkerEnabled { get; set; } + + [Required] + public bool CacheBootResources { get; set; } + + public ITaskItem[] ConfigurationFiles { get; set; } + + [Required] + public string OutputPath { get; set; } + + public override bool Execute() + { + using var fileStream = File.Create(OutputPath); + var entryAssemblyName = AssemblyName.GetAssemblyName(AssemblyPath).Name; + + try + { + WriteBootJson(fileStream, entryAssemblyName); + } + catch (Exception ex) + { + Log.LogErrorFromException(ex); + } + + return !Log.HasLoggedErrors; + } + + // Internal for tests + internal void WriteBootJson(Stream output, string entryAssemblyName) + { + var result = new BootJsonData + { + entryAssembly = entryAssemblyName, + cacheBootResources = CacheBootResources, + debugBuild = DebugBuild, + linkerEnabled = LinkerEnabled, + resources = new ResourcesData(), + config = new List<string>(), + }; + + // Build a two-level dictionary of the form: + // - assembly: + // - UriPath (e.g., "System.Text.Json.dll") + // - ContentHash (e.g., "4548fa2e9cf52986") + // - runtime: + // - UriPath (e.g., "dotnet.js") + // - ContentHash (e.g., "3448f339acf512448") + if (Resources != null) + { + var resourceData = result.resources; + foreach (var resource in Resources) + { + var resourceTypeMetadata = resource.GetMetadata("BootManifestResourceType"); + ResourceHashesByNameDictionary resourceList; + switch (resourceTypeMetadata) + { + case "runtime": + resourceList = resourceData.runtime; + break; + case "assembly": + resourceList = resourceData.assembly; + break; + case "pdb": + resourceData.pdb ??= new ResourceHashesByNameDictionary(); + resourceList = resourceData.pdb; + break; + case "satellite": + if (resourceData.satelliteResources is null) + { + resourceData.satelliteResources = new Dictionary<string, ResourceHashesByNameDictionary>(StringComparer.OrdinalIgnoreCase); + } + var resourceCulture = resource.GetMetadata("Culture"); + if (resourceCulture is null) + { + Log.LogWarning("Satellite resource {0} does not specify required metadata 'Culture'.", resource); + continue; + } + + if (!resourceData.satelliteResources.TryGetValue(resourceCulture, out resourceList)) + { + resourceList = new ResourceHashesByNameDictionary(); + resourceData.satelliteResources.Add(resourceCulture, resourceList); + } + break; + default: + throw new NotSupportedException($"Unsupported BootManifestResourceType metadata value: {resourceTypeMetadata}"); + } + + var resourceName = GetResourceName(resource); + if (!resourceList.ContainsKey(resourceName)) + { + resourceList.Add(resourceName, $"sha256-{resource.GetMetadata("Integrity")}"); + } + } + } + + if (ConfigurationFiles != null) + { + foreach (var configFile in ConfigurationFiles) + { + result.config.Add(Path.GetFileName(configFile.ItemSpec)); + } + } + + var serializer = new DataContractJsonSerializer(typeof(BootJsonData), new DataContractJsonSerializerSettings + { + UseSimpleDictionaryFormat = true + }); + + using var writer = JsonReaderWriterFactory.CreateJsonWriter(output, Encoding.UTF8, ownsStream: false, indent: true); + serializer.WriteObject(writer, result); + } + + private static string GetResourceName(ITaskItem item) + { + var name = item.GetMetadata("BootManifestResourceName"); + + if (string.IsNullOrEmpty(name)) + { + throw new Exception($"No BootManifestResourceName was specified for item '{item.ItemSpec}'"); + } + + return name.Replace('\\', '/'); + } + +#pragma warning disable IDE1006 // Naming Styles + /// <summary> + /// Defines the structure of a Blazor boot JSON file + /// </summary> + public class BootJsonData + { + /// <summary> + /// Gets the name of the assembly with the application entry point + /// </summary> + public string entryAssembly { get; set; } + + /// <summary> + /// Gets the set of resources needed to boot the application. This includes the transitive + /// closure of .NET assemblies (including the entrypoint assembly), the dotnet.wasm file, + /// and any PDBs to be loaded. + /// + /// Within <see cref="ResourceHashesByNameDictionary"/>, dictionary keys are resource names, + /// and values are SHA-256 hashes formatted in prefixed base-64 style (e.g., 'sha256-abcdefg...') + /// as used for subresource integrity checking. + /// </summary> + public ResourcesData resources { get; set; } = new ResourcesData(); + + /// <summary> + /// Gets a value that determines whether to enable caching of the <see cref="resources"/> + /// inside a CacheStorage instance within the browser. + /// </summary> + public bool cacheBootResources { get; set; } + + /// <summary> + /// Gets a value that determines if this is a debug build. + /// </summary> + public bool debugBuild { get; set; } + + /// <summary> + /// Gets a value that determines if the linker is enabled. + /// </summary> + public bool linkerEnabled { get; set; } + + /// <summary> + /// Config files for the application + /// </summary> + public List<string> config { get; set; } + } + + public class ResourcesData + { + /// <summary> + /// .NET Wasm runtime resources (dotnet.wasm, dotnet.js) etc. + /// </summary> + public ResourceHashesByNameDictionary runtime { get; set; } = new ResourceHashesByNameDictionary(); + + /// <summary> + /// "assembly" (.dll) resources + /// </summary> + public ResourceHashesByNameDictionary assembly { get; set; } = new ResourceHashesByNameDictionary(); + + /// <summary> + /// "debug" (.pdb) resources + /// </summary> + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary pdb { get; set; } + + /// <summary> + /// localization (.satellite resx) resources + /// </summary> + [DataMember(EmitDefaultValue = false)] + public Dictionary<string, ResourceHashesByNameDictionary> satelliteResources { get; set; } + } +#pragma warning restore IDE1006 // Naming Styles + } +} diff --git a/src/Components/WebAssembly/Build/src/Tasks/GenerateBlazorCompressionManifest.cs b/src/Components/WebAssembly/Build/src/Tasks/GenerateBlazorCompressionManifest.cs new file mode 100644 index 0000000000000000000000000000000000000000..e7a7b16ee273fd22b8df92bf5527496485214c53 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/Tasks/GenerateBlazorCompressionManifest.cs @@ -0,0 +1,102 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build +{ + public class GenerateBlazorCompressionManifest : Task + { + [Required] + public ITaskItem[] FilesToCompress { get; set; } + + [Required] + public string ManifestPath { get; set; } + + public override bool Execute() + { + try + { + WriteCompressionManifest(); + } + catch (Exception ex) + { + Log.LogErrorFromException(ex); + } + + return !Log.HasLoggedErrors; + } + + private void WriteCompressionManifest() + { + var tempFilePath = Path.GetTempFileName(); + + var manifest = new ManifestData(); + var filesToCompress = new List<CompressedFile>(); + + foreach (var file in FilesToCompress) + { + filesToCompress.Add(new CompressedFile + { + Source = file.GetMetadata("FullPath"), + InputSource = file.GetMetadata("InputSource"), + Target = file.GetMetadata("TargetCompressionPath"), + }); + } + + manifest.FilesToCompress = filesToCompress.ToArray(); + + var serializer = new DataContractJsonSerializer(typeof(ManifestData)); + + using (var tempFile = File.OpenWrite(tempFilePath)) + { + using (var writer = JsonReaderWriterFactory.CreateJsonWriter(tempFile, Encoding.UTF8, ownsStream: false, indent: true)) + { + serializer.WriteObject(writer, manifest); + } + } + + if (!File.Exists(ManifestPath)) + { + File.Move(tempFilePath, ManifestPath); + return; + } + + var originalText = File.ReadAllText(ManifestPath); + var newManifest = File.ReadAllText(tempFilePath); + if (!string.Equals(originalText, newManifest, StringComparison.Ordinal)) + { + // OnlyWriteWhenDifferent + File.Delete(ManifestPath); + File.Move(tempFilePath, ManifestPath); + } + } + + [DataContract] + private class ManifestData + { + [DataMember] + public CompressedFile[] FilesToCompress { get; set; } + } + + [DataContract] + private class CompressedFile + { + [DataMember] + public string Source { get; set; } + + [DataMember] + public string InputSource { get; set; } + + [DataMember] + public string Target { get; set; } + } + } +} diff --git a/src/Components/WebAssembly/Build/src/Tasks/GenerateServiceWorkerAssetsManifest.cs b/src/Components/WebAssembly/Build/src/Tasks/GenerateServiceWorkerAssetsManifest.cs new file mode 100644 index 0000000000000000000000000000000000000000..9a1885ad0c54b2dfe4a50228d3e949f1d5f58810 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/Tasks/GenerateServiceWorkerAssetsManifest.cs @@ -0,0 +1,82 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Linq; +using System.Runtime.Serialization.Json; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.AspNetCore.Blazor.Build +{ + public class GenerateServiceWorkerAssetsManifest : Task + { + [Required] + public string Version { get; set; } + + [Required] + public ITaskItem[] AssetsWithHashes { get; set; } + + [Required] + public string OutputPath { get; set; } + + public override bool Execute() + { + using var fileStream = File.Create(OutputPath); + WriteFile(fileStream); + return true; + } + + internal void WriteFile(Stream stream) + { + var data = new AssetsManifestFile + { + version = Version, + assets = AssetsWithHashes.Select(item => new AssetsManifestFileEntry + { + url = item.GetMetadata("AssetUrl"), + hash = $"sha256-{item.GetMetadata("Integrity")}", + }).ToArray() + }; + + using var streamWriter = new StreamWriter(stream, Encoding.UTF8, bufferSize: 50, leaveOpen: true); + streamWriter.Write("self.assetsManifest = "); + streamWriter.Flush(); + + using var jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.UTF8, ownsStream: false, indent: true); + new DataContractJsonSerializer(typeof(AssetsManifestFile)).WriteObject(jsonWriter, data); + jsonWriter.Flush(); + + streamWriter.WriteLine(";"); + } + +#pragma warning disable IDE1006 // Naming Styles + public class AssetsManifestFile + { + /// <summary> + /// Gets or sets a version string. + /// </summary> + public string version { get; set; } + + /// <summary> + /// Gets or sets the assets. Keys are URLs; values are base-64-formatted SHA256 content hashes. + /// </summary> + public AssetsManifestFileEntry[] assets { get; set; } + } + + public class AssetsManifestFileEntry + { + /// <summary> + /// Gets or sets the asset URL. Normally this will be relative to the application's base href. + /// </summary> + public string url { get; set; } + + /// <summary> + /// Gets or sets the file content hash. This should be the base-64-formatted SHA256 value. + /// </summary> + public string hash { get; set; } + } +#pragma warning restore IDE1006 // Naming Styles + } +} diff --git a/src/Components/Blazor/Build/src/Tasks/GenerateTypeGranularityLinkingConfig.cs b/src/Components/WebAssembly/Build/src/Tasks/GenerateTypeGranularityLinkingConfig.cs similarity index 96% rename from src/Components/Blazor/Build/src/Tasks/GenerateTypeGranularityLinkingConfig.cs rename to src/Components/WebAssembly/Build/src/Tasks/GenerateTypeGranularityLinkingConfig.cs index 8a56b7fc3deb3cfa0d192749710d26c0aa3590f2..66e673501e4624f91153707f0224095c2b829c64 100644 --- a/src/Components/Blazor/Build/src/Tasks/GenerateTypeGranularityLinkingConfig.cs +++ b/src/Components/WebAssembly/Build/src/Tasks/GenerateTypeGranularityLinkingConfig.cs @@ -6,7 +6,7 @@ using System.Xml.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Microsoft.AspNetCore.Blazor.Build.Tasks +namespace Microsoft.AspNetCore.Components.WebAssembly.Build.Tasks { public class GenerateTypeGranularityLinkingConfig : Task { diff --git a/src/Components/WebAssembly/Build/src/Tasks/GzipCompressBlazorApplicationFiles.cs b/src/Components/WebAssembly/Build/src/Tasks/GzipCompressBlazorApplicationFiles.cs new file mode 100644 index 0000000000000000000000000000000000000000..53d26328a344d734d9b920a1ada261558e5b0915 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/Tasks/GzipCompressBlazorApplicationFiles.cs @@ -0,0 +1,89 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.IO.Compression; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build +{ + public class GzipCompressBlazorApplicationFiles : Task + { + [Required] + public string ManifestPath { get; set; } + + public override bool Execute() + { + var serializer = new DataContractJsonSerializer(typeof(ManifestData)); + + ManifestData manifest = null; + using (var tempFile = File.OpenRead(ManifestPath)) + { + manifest = (ManifestData)serializer.ReadObject(tempFile); + } + + System.Threading.Tasks.Parallel.ForEach(manifest.FilesToCompress, (file) => + { + var inputPath = file.Source; + var inputSource = file.InputSource; + var targetCompressionPath = file.Target; + + if (!File.Exists(inputSource)) + { + Log.LogMessage($"Skipping '{inputPath}' because '{inputSource}' does not exist."); + return; + } + + if (File.Exists(targetCompressionPath) && File.GetLastWriteTimeUtc(inputSource) < File.GetLastWriteTimeUtc(targetCompressionPath)) + { + // Incrementalism. If input source doesn't exist or it exists and is not newer than the expected output, do nothing. + Log.LogMessage($"Skipping '{inputPath}' because '{targetCompressionPath}' is newer than '{inputSource}'."); + return; + } + + try + { + Directory.CreateDirectory(Path.GetDirectoryName(targetCompressionPath)); + + using var sourceStream = File.OpenRead(inputPath); + using var fileStream = new FileStream(targetCompressionPath, FileMode.Create); + using var stream = new GZipStream(fileStream, CompressionLevel.Optimal); + + sourceStream.CopyTo(stream); + } + catch (Exception e) + { + Log.LogErrorFromException(e); + throw; + } + }); + + return !Log.HasLoggedErrors; + } + + [DataContract] + private class ManifestData + { + [DataMember] + public CompressedFile[] FilesToCompress { get; set; } + } + + [DataContract] + private class CompressedFile + { + [DataMember] + public string Source { get; set; } + + [DataMember] + public string InputSource { get; set; } + + [DataMember] + public string Target { get; set; } + } + } +} + diff --git a/src/Components/Blazor/Build/src/Tasks/ResolveBlazorRuntimeDependencies.cs b/src/Components/WebAssembly/Build/src/Tasks/ResolveBlazorRuntimeDependencies.cs similarity index 99% rename from src/Components/Blazor/Build/src/Tasks/ResolveBlazorRuntimeDependencies.cs rename to src/Components/WebAssembly/Build/src/Tasks/ResolveBlazorRuntimeDependencies.cs index 1181ea337d72a84e3678fc8d1b0563ded44581ea..4598432a1c7ca732c2a73b3f2400e4a9500ac0a9 100644 --- a/src/Components/Blazor/Build/src/Tasks/ResolveBlazorRuntimeDependencies.cs +++ b/src/Components/WebAssembly/Build/src/Tasks/ResolveBlazorRuntimeDependencies.cs @@ -12,7 +12,7 @@ using System.Reflection.PortableExecutable; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Microsoft.AspNetCore.Blazor.Build +namespace Microsoft.AspNetCore.Components.WebAssembly.Build { public class ResolveBlazorRuntimeDependencies : Task { diff --git a/src/Components/WebAssembly/Build/src/build/netstandard1.0/Microsoft.AspNetCore.Components.WebAssembly.Build.props b/src/Components/WebAssembly/Build/src/build/netstandard1.0/Microsoft.AspNetCore.Components.WebAssembly.Build.props new file mode 100644 index 0000000000000000000000000000000000000000..a85c05ce87016a88fc1a58ff5ee8a3d3fbf7185d --- /dev/null +++ b/src/Components/WebAssembly/Build/src/build/netstandard1.0/Microsoft.AspNetCore.Components.WebAssembly.Build.props @@ -0,0 +1,7 @@ +<Project> + <Import Project="$(MSBuildThisFileDirectory)..\..\targets\All.props" /> + + <PropertyGroup> + <ReferencesComponentsWebAssemblyBuild>true</ReferencesComponentsWebAssemblyBuild> + </PropertyGroup> +</Project> diff --git a/src/Components/Blazor/Build/src/build/netstandard1.0/Microsoft.AspNetCore.Blazor.Build.targets b/src/Components/WebAssembly/Build/src/build/netstandard1.0/Microsoft.AspNetCore.Components.WebAssembly.Build.targets similarity index 100% rename from src/Components/Blazor/Build/src/build/netstandard1.0/Microsoft.AspNetCore.Blazor.Build.targets rename to src/Components/WebAssembly/Build/src/build/netstandard1.0/Microsoft.AspNetCore.Components.WebAssembly.Build.targets diff --git a/src/Components/Blazor/Build/src/targets/All.props b/src/Components/WebAssembly/Build/src/targets/All.props similarity index 54% rename from src/Components/Blazor/Build/src/targets/All.props rename to src/Components/WebAssembly/Build/src/targets/All.props index d1d242f4d92fca434bedaa61233350a29e0bc015..8315d3cb409fbf927dc64584353cd5f37beeffeb 100644 --- a/src/Components/Blazor/Build/src/targets/All.props +++ b/src/Components/WebAssembly/Build/src/targets/All.props @@ -1,10 +1,16 @@ <Project> <Import Project="Blazor.MonoRuntime.props" /> + <Import Project="StaticWebAssets.props" /> + <PropertyGroup> - <DefaultWebContentItemExcludes>$(DefaultWebContentItemExcludes);wwwroot\**</DefaultWebContentItemExcludes> + + <!-- Assets for a Blazor app are exposed on the root folder by default. --> + <StaticWebAssetBasePath>/</StaticWebAssetBasePath> <!-- When using IISExpress with a standalone app, there's no point restarting IISExpress after build. It slows things unnecessarily and breaks in-flight HTTP requests. --> <NoRestartServerOnBuild>true</NoRestartServerOnBuild> + + <BlazorEnableCompression Condition="'$(BlazorEnableCompression)' == ''">true</BlazorEnableCompression> </PropertyGroup> </Project> diff --git a/src/Components/WebAssembly/Build/src/targets/All.targets b/src/Components/WebAssembly/Build/src/targets/All.targets new file mode 100644 index 0000000000000000000000000000000000000000..5966cec27feae74aec581902a1e2fd53a23deef7 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/targets/All.targets @@ -0,0 +1,21 @@ +<Project> + <PropertyGroup> + <_BlazorToolsDir Condition="'$(_BlazorToolsDir)' == ''">$(MSBuildThisFileDirectory)..\tools\</_BlazorToolsDir> + <_BlazorTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">netcoreapp</_BlazorTasksTFM> + <_BlazorTasksTFM Condition=" '$(_BlazorTasksTFM)' == ''">netfx</_BlazorTasksTFM> + <_BlazorTasksPath>$(_BlazorToolsDir)$(_BlazorTasksTFM)\Microsoft.AspNetCore.Components.WebAssembly.Build.Tasks.dll</_BlazorTasksPath> + + <!-- The Blazor build code can only find your referenced assemblies if they are in the output directory --> + <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> + + <!-- By default, enable debugging for debug builds. --> + <BlazorEnableDebugging Condition="'$(Configuration)' == 'Debug' AND '$(BlazorEnableDebugging)' == ''">true</BlazorEnableDebugging> + </PropertyGroup> + + <Import Project="Blazor.MonoRuntime.targets" /> + <Import Project="Publish.targets" /> + <Import Project="StaticWebAssets.targets" /> + <Import Project="ServiceWorkerAssetsManifest.targets" /> + <Import Project="Compression.targets" Condition="'$(BlazorEnableCompression)' == 'true'" /> + +</Project> diff --git a/src/Components/WebAssembly/Build/src/targets/Blazor.MonoRuntime.props b/src/Components/WebAssembly/Build/src/targets/Blazor.MonoRuntime.props new file mode 100644 index 0000000000000000000000000000000000000000..453deddacd3eb0f0f2e9cae6e3f79a8c6fda4fd6 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/targets/Blazor.MonoRuntime.props @@ -0,0 +1,20 @@ +<Project> + + <PropertyGroup> + <BlazorWebAssemblyI18NAssemblies>none</BlazorWebAssemblyI18NAssemblies> <!-- See Mono linker docs - allows comma-separated values from: none,all,cjk,mideast,other,rare,west --> + <AdditionalMonoLinkerOptions>--disable-opt unreachablebodies --verbose --strip-security true --exclude-feature com -v false -c link -u link -b true</AdditionalMonoLinkerOptions> + + <_BlazorJsPath Condition="'$(_BlazorJsPath)' == ''">$(MSBuildThisFileDirectory)..\tools\blazor\blazor.webassembly.js</_BlazorJsPath> + <_BaseBlazorRuntimeOutputPath>_framework\</_BaseBlazorRuntimeOutputPath> + <_BlazorRuntimeBinOutputPath>$(_BaseBlazorRuntimeOutputPath)_bin\</_BlazorRuntimeBinOutputPath> + <_BlazorRuntimeWasmOutputPath>$(_BaseBlazorRuntimeOutputPath)wasm\</_BlazorRuntimeWasmOutputPath> + <_BlazorBuiltInBclLinkerDescriptor>$(MSBuildThisFileDirectory)BuiltInBclLinkerDescriptor.xml</_BlazorBuiltInBclLinkerDescriptor> + <_BlazorCollationLinkerDescriptor>$(MSBuildThisFileDirectory)CollationLinkerDescriptor.xml</_BlazorCollationLinkerDescriptor> + <_BlazorBootJsonName>blazor.boot.json</_BlazorBootJsonName> + </PropertyGroup> + + <ItemGroup> + <BlazorLinkerDescriptor Include="$(_BlazorBuiltInBclLinkerDescriptor)" /> + </ItemGroup> + +</Project> diff --git a/src/Components/WebAssembly/Build/src/targets/Blazor.MonoRuntime.targets b/src/Components/WebAssembly/Build/src/targets/Blazor.MonoRuntime.targets new file mode 100644 index 0000000000000000000000000000000000000000..7d6eb810b4675c389190080dfffe288db3672a3f --- /dev/null +++ b/src/Components/WebAssembly/Build/src/targets/Blazor.MonoRuntime.targets @@ -0,0 +1,488 @@ +<Project> + <UsingTask TaskName="BlazorGetFileHash" AssemblyFile="$(_BlazorTasksPath)" /> + + <PropertyGroup> + <BlazorWebAssemblyEnableLinking Condition="'$(BlazorWebAssemblyEnableLinking)' == '' AND '$(Configuration)' != 'Debug'">true</BlazorWebAssemblyEnableLinking> + <BlazorWebAssemblyEnableLinking Condition="'$(BlazorWebAssemblyEnableLinking)' == ''">false</BlazorWebAssemblyEnableLinking> + </PropertyGroup> + + <Target + Name="_PrepareBlazorOutputs" + DependsOnTargets="_ResolveBlazorInputs;_ResolveBlazorOutputs;_GenerateBlazorBootJson;_GenerateBlazorBootJsonIntegrity"> + </Target> + + <Target Name="_ResolveBlazorInputs" DependsOnTargets="ResolveReferences;ResolveRuntimePackAssets"> + <Error Text="BlazorLinkOnBuild has been renamed to BlazorWebAssemblyEnableLinking. Please update your project files to use the new property." + Condition="'$(BlazorLinkOnBuild)' != ''" /> + + <PropertyGroup> + <!-- /obj/<<configuration>>/<<targetframework>>/blazor --> + <_BlazorIntermediateOutputPath>$(IntermediateOutputPath)blazor\</_BlazorIntermediateOutputPath> + + <!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker.descriptor.xml --> + <_GeneratedBlazorLinkerDescriptor>$(_BlazorIntermediateOutputPath)linker.descriptor.xml</_GeneratedBlazorLinkerDescriptor> + + <_TypeGranularityLinkerDescriptor>$(_BlazorIntermediateOutputPath)linker.typegranularityconfig.xml</_TypeGranularityLinkerDescriptor> + + <!-- /obj/<<configuration>>/<<targetframework>>/blazor/linker/ --> + <_BlazorIntermediateLinkerOutputPath>$(_BlazorIntermediateOutputPath)linker/</_BlazorIntermediateLinkerOutputPath> + + <!-- /obj/<<configuration>>/<<targetframework>>/blazor/blazor.boot.json --> + <_BlazorBootJsonIntermediateOutputPath>$(_BlazorIntermediateOutputPath)$(_BlazorBootJsonName)</_BlazorBootJsonIntermediateOutputPath> + + <_BlazorLinkerOutputCache>$(_BlazorIntermediateOutputPath)linker.output</_BlazorLinkerOutputCache> + + <_BlazorApplicationAssembliesCacheFile>$(_BlazorIntermediateOutputPath)unlinked.output</_BlazorApplicationAssembliesCacheFile> + </PropertyGroup> + + <!-- + When running from Desktop MSBuild, DOTNET_HOST_PATH is not set. + In this case, explicitly specify the path to the dotnet host. + --> + <PropertyGroup Condition=" '$(DOTNET_HOST_PATH)' == '' "> + <_DotNetHostDirectory>$(NetCoreRoot)</_DotNetHostDirectory> + <_DotNetHostFileName>dotnet</_DotNetHostFileName> + <_DotNetHostFileName Condition=" '$(OS)' == 'Windows_NT' ">dotnet.exe</_DotNetHostFileName> + </PropertyGroup> + + <ItemGroup> + <_WebAssemblyBCLFolder Include=" + $(ComponentsWebAssemblyBaseClassLibraryPath); + $(ComponentsWebAssemblyBaseClassLibraryFacadesPath); + $(ComponentsWebAssemblyFrameworkPath)" /> + + <_WebAssemblyBCLAssembly Include="%(_WebAssemblyBCLFolder.Identity)*.dll" /> + + <_BlazorConfigFile Include="wwwroot\appsettings*.json" /> + </ItemGroup> + + <!-- + Calculate the assemblies that act as inputs to calculate assembly closure. Based on _ComputeAssembliesToPostprocessOnPublish which is used as input to SDK's linker + https://github.com/dotnet/sdk/blob/d597e7b09d7657ba4e326d6734e14fcbf8473564/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Publish.targets#L864-L873 + --> + <ItemGroup> + <!-- Assemblies from packages --> + <_BlazorManagedRuntimeAssembly Include="@(RuntimeCopyLocalItems)" /> + + <!-- Assemblies from other references --> + <_BlazorUserRuntimeAssembly Include="@(ReferencePath->WithMetadataValue('CopyLocal', 'true'))" /> + <_BlazorUserRuntimeAssembly Include="@(ReferenceDependencyPaths->WithMetadataValue('CopyLocal', 'true'))" /> + + <_BlazorManagedRuntimeAssembly Include="@(_BlazorUserRuntimeAssembly)" /> + <_BlazorManagedRuntimeAssembly Include="@(IntermediateAssembly)" /> + </ItemGroup> + + <MakeDir Directories="$(_BlazorIntermediateOutputPath)" /> + </Target> + + <UsingTask TaskName="BlazorWriteSatelliteAssemblyFile" AssemblyFile="$(_BlazorTasksPath)" /> + <UsingTask TaskName="BlazorReadSatelliteAssemblyFile" AssemblyFile="$(_BlazorTasksPath)" /> + + <Target Name="_ResolveBlazorOutputs" DependsOnTargets="_ResolveBlazorOutputsWhenLinked;_ResolveBlazorOutputsWhenNotLinked"> + <!-- + These are the items calculated as the closure of the runtime assemblies, either by calling the linker + or by calling our custom ResolveBlazorRuntimeDependencies task if the linker was disabled. Other than + satellite assemblies, this should include all assemblies needed to run the application. + --> + <ItemGroup> + <_BlazorJSFile Include="$(_BlazorJSPath)" /> + <_BlazorJSFile Include="$(_BlazorJSMapPath)" Condition="Exists('$(_BlazorJSMapPath)')" /> + + <_DotNetWasmRuntimeFile Include="$(ComponentsWebAssemblyRuntimePath)*"/> + <_DotNetWasmRuntimeFile + Remove="%(Identity)" + Condition="'$(BlazorEnableTimeZoneSupport)' == 'false' AND '%(FileName)%(Extension)' == 'dotnet.timezones.dat'" /> + + <!-- + ReferenceCopyLocalPaths includes all files that are part of the build out with CopyLocalLockFileAssemblies on. + Remove assemblies that are inputs to calculating the assembly closure. Instead use the resolved outputs, since it is the minimal set. + + ReferenceCopyLocalPaths also includes satellite assemblies from referenced projects but are inexpicably missing + any metadata that might allow them to be differentiated. We'll explicitly add those + to _BlazorOutputWithTargetPath so that satellite assemblies from packages, the current project and referenced project + are all treated the same. + --> + <_BlazorCopyLocalPaths Include="@(ReferenceCopyLocalPaths)" + Exclude="@(_BlazorManagedRuntimeAssembly);@(ReferenceSatellitePaths)" + Condition="'%(Extension)' == '.dll'" /> + + <_BlazorCopyLocalPaths Include="@(IntermediateSatelliteAssembliesWithTargetPath)"> + <DestinationSubDirectory>%(IntermediateSatelliteAssembliesWithTargetPath.Culture)\</DestinationSubDirectory> + </_BlazorCopyLocalPaths> + + <_BlazorOutputWithTargetPath Include="@(_BlazorCopyLocalPaths)"> + <!-- This group is for satellite assemblies. We set the resource name to include a path, e.g. "fr\\SomeAssembly.resources.dll" --> + <BootManifestResourceType Condition="'%(_BlazorCopyLocalPaths.Extension)' == '.pdb'">pdb</BootManifestResourceType> + <BootManifestResourceType Condition="'%(_BlazorCopyLocalPaths.Culture)' == '' AND '%(_BlazorCopyLocalPaths.Extension)' == '.dll'">assembly</BootManifestResourceType> + <BootManifestResourceType Condition="'%(_BlazorCopyLocalPaths.Culture)' != '' AND '%(_BlazorCopyLocalPaths.Extension)' == '.dll'">satellite</BootManifestResourceType> + <BootManifestResourceName>%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</BootManifestResourceName> + <TargetOutputPath>$(_BlazorRuntimeBinOutputPath)%(_BlazorCopyLocalPaths.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath> + </_BlazorOutputWithTargetPath> + + <_BlazorOutputWithTargetPath Include="@(ReferenceSatellitePaths)"> + <Culture>$([System.String]::Copy('%(ReferenceSatellitePaths.DestinationSubDirectory)').Trim('\').Trim('/'))</Culture> + <BootManifestResourceType>satellite</BootManifestResourceType> + <BootManifestResourceName>%(ReferenceSatellitePaths.DestinationSubDirectory)%(FileName)%(Extension)</BootManifestResourceName> + <TargetOutputPath>$(_BlazorRuntimeBinOutputPath)%(ReferenceSatellitePaths.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath> + </_BlazorOutputWithTargetPath> + + <_BlazorOutputWithTargetPath Include="@(_BlazorResolvedAssembly)"> + <BootManifestResourceType Condition="'%(Extension)' == '.dll'">assembly</BootManifestResourceType> + <BootManifestResourceType Condition="'%(Extension)' == '.pdb'">pdb</BootManifestResourceType> + <BootManifestResourceName>%(FileName)%(Extension)</BootManifestResourceName> + <TargetOutputPath>$(_BlazorRuntimeBinOutputPath)%(FileName)%(Extension)</TargetOutputPath> + </_BlazorOutputWithTargetPath> + + <_BlazorOutputWithTargetPath Include="@(_DotNetWasmRuntimeFile)"> + <TargetOutputPath>$(_BlazorRuntimeWasmOutputPath)%(FileName)%(Extension)</TargetOutputPath> + <BootManifestResourceType>runtime</BootManifestResourceType> + <BootManifestResourceName>%(FileName)%(Extension)</BootManifestResourceName> + </_BlazorOutputWithTargetPath> + + <_BlazorOutputWithTargetPath Include="@(_BlazorJSFile)"> + <TargetOutputPath>$(_BaseBlazorRuntimeOutputPath)%(FileName)%(Extension)</TargetOutputPath> + </_BlazorOutputWithTargetPath> + + <_BlazorWriteSatelliteAssembly Include="@(_BlazorOutputWithTargetPath->WithMetadataValue('BootManifestResourceType', 'satellite'))" /> + </ItemGroup> + + <!-- + When building with BuildingProject=false, satellite assemblies do not get resolved (the ones for the current project and the one for + referenced project). BuildingProject=false is typically set for referenced projects when building inside VisualStudio. + To workaround this, we'll stash metadata during a regular build, and rehydrate from it when BuildingProject=false. + --> + + <PropertyGroup> + <_BlazorSatelliteAssemblyStashFile>$(_BlazorIntermediateOutputPath)blazor.satelliteasm.props</_BlazorSatelliteAssemblyStashFile> + </PropertyGroup> + + <BlazorWriteSatelliteAssemblyFile + SatelliteAssembly="@(_BlazorWriteSatelliteAssembly)" + WriteFile="$(_BlazorSatelliteAssemblyStashFile)" + Condition="'$(BuildingProject)' == 'true' AND '@(_BlazorWriteSatelliteAssembly->Count())' != '0'" /> + + <Delete + Files="$(_BlazorSatelliteAssemblyStashFile)" + Condition="'$(BuildingProject)' == 'true' AND '@(_BlazorWriteSatelliteAssembly->Count())' == '0' and EXISTS('$(_BlazorSatelliteAssemblyStashFile)')" /> + + <BlazorReadSatelliteAssemblyFile + ReadFile="$(_BlazorSatelliteAssemblyStashFile)" + Condition="'$(BuildingProject)' != 'true' AND EXISTS('$(_BlazorSatelliteAssemblyStashFile)')"> + <Output TaskParameter="SatelliteAssembly" ItemName="_BlazorReadSatelliteAssembly" /> + </BlazorReadSatelliteAssemblyFile> + + <ItemGroup> + <FileWrites Include="$(_BlazorSatelliteAssemblyStashFile)" Condition="Exists('$(_BlazorSatelliteAssemblyStashFile)')" /> + </ItemGroup> + + <ItemGroup Condition="'@(_BlazorReadSatelliteAssembly->Count())' != '0'"> + <!-- We've imported a previously stashed file. Let's turn in to a _BlazorOutputWithTargetPath --> + <_BlazorOutputWithTargetPath Include="@(_BlazorReadSatelliteAssembly)"> + <BootManifestResourceType>satellite</BootManifestResourceType> + <BootManifestResourceName>%(_BlazorReadSatelliteAssembly.DestinationSubDirectory)%(FileName)%(Extension)</BootManifestResourceName> + <TargetOutputPath>$(_BlazorRuntimeBinOutputPath)%(_BlazorReadSatelliteAssembly.DestinationSubDirectory)%(FileName)%(Extension)</TargetOutputPath> + </_BlazorOutputWithTargetPath> + </ItemGroup> + + <!-- + We need to know at build time (not publish time) whether or not to include pdbs in the + blazor.boot.json file, so this is controlled by the BlazorEnableDebugging flag, whose + default value is determined by the build configuration. + --> + <ItemGroup Condition="'$(BlazorEnableDebugging)' != 'true'"> + <_BlazorOutputWithTargetPath Remove="@(_BlazorOutputWithTargetPath)" Condition="'%(Extension)' == '.pdb'" /> + </ItemGroup> + + <ItemGroup> + <_ExistingBlazorOutputWithTargetPath Include="@(_BlazorOutputWithTargetPath)" Condition="Exists('%(FullPath)')" /> + </ItemGroup> + + <BlazorGetFileHash Files="@(_ExistingBlazorOutputWithTargetPath)" Algorithm="SHA256" HashEncoding="base64"> + <Output TaskParameter="Items" ItemName="_BlazorOutputWithHash" /> + </BlazorGetFileHash> + + <ItemGroup> + <_BlazorOutputWithIntegrity Include="@(_BlazorOutputWithHash)"> + <Integrity>%(_BlazorOutputWithHash.FileHash)</Integrity> + <IntegrityFile>$(IntermediateOutputPath)integrity\$([System.String]::Copy('%(FileHash)').Replace('/','-').Replace('+','_')).hash</IntegrityFile> + </_BlazorOutputWithIntegrity> + + <_BlazorOutputWithTargetPath Remove="@(_BlazorOutputWithIntegrity)" /> + <_BlazorOutputWithTargetPath Include="@(_BlazorOutputWithIntegrity)" RemoveMetadata="FileHash;FileHashAlgorithm" /> + + <MakeDir Directories="$(IntermediateOutputPath)integrity" /> + </ItemGroup> + + <WriteLinesToFile Lines="%(_BlazorOutputWithIntegrity.Integrity)" File="%(_BlazorOutputWithIntegrity.IntegrityFile)" WriteOnlyWhenDifferent="true" Overwrite="true" /> + + <ItemGroup> + <FileWrites Include="%(_BlazorOutputWithIntegrity.IntegrityFile)" /> + </ItemGroup> + + </Target> + + <!-- + Linker enabled part of the pipeline: + + * If there are no descriptors defined, generate a new linker descriptor. + * Invoke the linker and write linked files to a well-known directory. + * Collect the outputs of the linker. + --> + + <Target + Name="_ResolveBlazorOutputsWhenLinked" + Condition="'$(BlazorWebAssemblyEnableLinking)' == 'true'" + DependsOnTargets="_PrepareBlazorLinkerInputs;_GenerateBlazorLinkerDescriptor;_GenerateTypeGranularLinkerDescriptor;_LinkBlazorApplication"> + + <!-- _BlazorLinkerOutputCache records files linked during the last incremental build of the target. Read the contents and assign linked files to be copied to the output. --> + <ReadLinesFromFile File="$(_BlazorLinkerOutputCache)"> + <Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/> + </ReadLinesFromFile> + </Target> + + <Target Name="_PrepareBlazorLinkerInputs"> + <ItemGroup> + <_BlazorRuntimeCopyLocalItems Include="@(RuntimeCopyLocalItems)" /> + + <!-- + Any assembly from a package reference that starts with System. file name is allowed to be linked. + Assemblies from Microsoft.AspNetCore and Microsoft.Extensions, are also linked but with TypeGranularity. + --> + <_BlazorRuntimeCopyLocalItems IsLinkable="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('System.'))" /> + <_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))" /> + <_BlazorRuntimeCopyLocalItems IsLinkable="true" TypeGranularity="true" Condition="$([System.String]::Copy('%(Filename)').StartsWith('Microsoft.Extensions.'))" /> + + <_BlazorAssemblyToLink Include="@(_WebAssemblyBCLAssembly)" /> + <_BlazorAssemblyToLink Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' == 'true'" /> + + <_BlazorLinkerRoot Include="@(IntermediateAssembly)" /> + <_BlazorLinkerRoot Include="@(_BlazorUserRuntimeAssembly)" /> + <_BlazorLinkerRoot Include="@(_BlazorRuntimeCopyLocalItems)" Condition="'%(_BlazorRuntimeCopyLocalItems.IsLinkable)' != 'true'" /> + </ItemGroup> + + <!-- When specifically requested, include the linker substitutions file that strips out collation information.--> + <PropertyGroup Condition="'$(BlazorWebAssemblyPreserveCollationData)' == 'false'"> + <AdditionalMonoLinkerOptions>$(AdditionalMonoLinkerOptions) --substitutions "$(_BlazorCollationLinkerDescriptor)"</AdditionalMonoLinkerOptions> + </PropertyGroup> + </Target> + + <UsingTask TaskName="BlazorCreateRootDescriptorFile" AssemblyFile="$(_BlazorTasksPath)" /> + <Target Name="_GenerateBlazorLinkerDescriptor" + Inputs="@(IntermediateAssembly)" + Outputs="$(_GeneratedBlazorLinkerDescriptor)"> + + <!-- Generate linker descriptors if the project doesn't explicitly provide one. --> + + <BlazorCreateRootDescriptorFile + AssemblyNames="@(IntermediateAssembly->'%(Filename)')" + RootDescriptorFilePath="$(_GeneratedBlazorLinkerDescriptor)" /> + + <ItemGroup> + <FileWrites Include="$(_GeneratedBlazorLinkerDescriptor)" /> + <BlazorLinkerDescriptor Include="$(_GeneratedBlazorLinkerDescriptor)" /> + </ItemGroup> + </Target> + + <UsingTask TaskName="GenerateTypeGranularityLinkingConfig" AssemblyFile="$(_BlazorTasksPath)" /> + <Target Name="_GenerateTypeGranularLinkerDescriptor" + Inputs="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))" + Outputs="$(_TypeGranularityLinkerDescriptor)"> + + <GenerateTypeGranularityLinkingConfig + Assemblies="@(_BlazorAssemblyToLink->WithMetadataValue('TypeGranularity', 'true'))" + OutputPath="$(_TypeGranularityLinkerDescriptor)" /> + + <ItemGroup> + <BlazorLinkerDescriptor Include="$(_TypeGranularityLinkerDescriptor)" /> + <FileWrites Include="$(_TypeGranularityLinkerDescriptor)" /> + </ItemGroup> + </Target> + + <!-- + Note that the VS-specific condition below is a workaround for https://github.com/dotnet/aspnetcore/issues/19822 + For more details, see https://github.com/dotnet/aspnetcore/issues/20413 + --> + <UsingTask TaskName="BlazorILLink" AssemblyFile="$(_BlazorTasksPath)" /> + <Target + Name="_LinkBlazorApplication" + Inputs="$(ProjectAssetsFile); + @(_BlazorManagedRuntimeAssembly); + @(BlazorLinkerDescriptor); + $(MSBuildAllProjects)" + Outputs="$(_BlazorLinkerOutputCache)" + Condition="'$(BuildingInsideVisualStudio)' != 'true' OR '$(DeployOnBuild)' != 'true'"> + + <PropertyGroup> + <_BlazorLinkerAdditionalOptions>-l $(BlazorWebAssemblyI18NAssemblies) $(AdditionalMonoLinkerOptions)</_BlazorLinkerAdditionalOptions> + </PropertyGroup> + + <ItemGroup> + <_OldLinkedFile Include="$(_BlazorIntermediateLinkerOutputPath)*.dll" /> + <_OldLinkedFile Include="$(_BlazorIntermediateLinkerOutputPath)*.pdb" /> + </ItemGroup> + + <Delete Files="@(_OldLinkedFile)" /> + + <BlazorILLink + ILLinkPath="$(ComponentsWebAssemblyLinkerPath)" + AssemblyPaths="@(_BlazorAssemblyToLink)" + RootAssemblyNames="@(_BlazorLinkerRoot)" + RootDescriptorFiles="@(BlazorLinkerDescriptor)" + OutputDirectory="$(_BlazorIntermediateLinkerOutputPath)" + ExtraArgs="$(_BlazorLinkerAdditionalOptions)" + ToolExe="$(_DotNetHostFileName)" + ToolPath="$(_DotNetHostDirectory)" /> + + <ItemGroup> + <_LinkerResult Include="$(_BlazorIntermediateLinkerOutputPath)*.dll" /> + <_LinkerResult Include="$(_BlazorIntermediateLinkerOutputPath)*.pdb" /> + </ItemGroup> + + <WriteLinesToFile File="$(_BlazorLinkerOutputCache)" Lines="@(_LinkerResult)" Overwrite="true" /> + </Target> + + <UsingTask TaskName="ResolveBlazorRuntimeDependencies" AssemblyFile="$(_BlazorTasksPath)" /> + <Target + Name="_ResolveBlazorOutputsWhenNotLinked" + DependsOnTargets="_ResolveBlazorRuntimeDependencies" + Condition="'$(BlazorWebAssemblyEnableLinking)' != 'true'"> + + <ReadLinesFromFile File="$(_BlazorApplicationAssembliesCacheFile)" Condition="'@(_BlazorResolvedAssembly->Count())' == '0'"> + <Output TaskParameter="Lines" ItemName="_BlazorResolvedAssembly"/> + </ReadLinesFromFile> + + <ItemGroup> + <!-- + Workaround for https://github.com/dotnet/aspnetcore/issues/19926. Add _BlazorResolvedAssembly to FileWrites so that these files + do not get removed during an incremental build. Note that we add these files here as opposed to _ResolveBlazorRuntimeDependencies + since the task that calculates these items in _ResolveBlazorRuntimeDependencies does not execute in incremental builds. + --> + <FileWrites Include="$(_BlazorResolvedAssembly)"/> + </ItemGroup> + </Target> + + <Target + Name="_ResolveBlazorRuntimeDependencies" + Inputs="$(ProjectAssetsFile); + @(IntermediateAssembly); + @(_BlazorManagedRuntimeAssembly)" + Outputs="$(_BlazorApplicationAssembliesCacheFile)"> + + <!-- + At this point we have decided not to run the linker and instead to just copy the assemblies + from the BCL referenced by the app the nuget package into the _framework/_bin folder. + The only thing we need to do here is collect the list of items that will go into _framework/_bin. + --> + <ResolveBlazorRuntimeDependencies + EntryPoint="@(IntermediateAssembly)" + ApplicationDependencies="@(_BlazorManagedRuntimeAssembly)" + WebAssemblyBCLAssemblies="@(_WebAssemblyBCLAssembly)"> + + <Output TaskParameter="Dependencies" ItemName="_BlazorResolvedAssemblyUnlinked" /> + </ResolveBlazorRuntimeDependencies> + + <ItemGroup Condition="'$(BlazorWebAssemblyI18NAssemblies)' != 'none'"> + <!-- + Unless the user has asked for no-assemblies, copy all I18N assemblies to the build output. + We do not want to decipher the linker's format for passing in I18N options in our build targets + --> + <_BlazorResolvedAssemblyUnlinked Include="$(ComponentsWebAssemblyBaseClassLibraryPath)I18N*.dll" /> + </ItemGroup> + + <!-- + Workaround for https://github.com/dotnet/aspnetcore/issues/19926. Using the files from their initial locations + as-is causes RazorSDK to remove files from a hosted app's publish directory. This is because files being removed + are looked up by their path. We'll copy files to an intermediate location to avoid the removal code. + --> + <Copy + SourceFiles="@(_BlazorResolvedAssemblyUnlinked)" + DestinationFolder="$(_BlazorIntermediateOutputPath)unlinked"> + <Output TaskParameter="CopiedFiles" ItemName="_BlazorResolvedAssembly" /> + </Copy> + + <WriteLinesToFile File="$(_BlazorApplicationAssembliesCacheFile)" Lines="@(_BlazorResolvedAssembly)" Overwrite="true" /> + + <ItemGroup> + <FileWrites Include="$(_BlazorApplicationAssembliesCacheFile)" /> + </ItemGroup> + </Target> + + <Target Name="_GenerateBlazorBootJsonInputHash"> + <ItemGroup> + <_BlazorBootJsonHashInput Include="@(IntermediateAssembly)" /> + <_BlazorBootJsonHashInput Include="@(_BlazorOutputWithTargetPath)" /> + <_BlazorBootJsonHashInput Include="@(_BlazorConfigFile)" /> + </ItemGroup> + + <Hash ItemsToHash="@(_BlazorBootJsonHashInput)"> + <Output TaskParameter="HashResult" PropertyName="_BlazorBootJsonInputHash" /> + </Hash> + + <PropertyGroup> + <_BlazorBootJsonInputHashFile>$(_BlazorIntermediateOutputPath)boot.json.input</_BlazorBootJsonInputHashFile> + </PropertyGroup> + + <WriteLinesToFile + Lines="$(_BlazorBootJsonInputHash)" + File="$(_BlazorBootJsonInputHashFile)" + Overwrite="True" + WriteOnlyWhenDifferent="True" /> + + </Target> + + <UsingTask TaskName="GenerateBlazorBootJson" AssemblyFile="$(_BlazorTasksPath)" /> + + <Target + Name="_GenerateBlazorBootJson" + DependsOnTargets="_GenerateBlazorBootJsonInputHash" + Inputs="$(MSBuildAllProjects);@(_BlazorOutputWithTargetPath);$(_BlazorBootJsonInputHashFile)" + Outputs="$(_BlazorBootJsonIntermediateOutputPath)"> + + <PropertyGroup> + <_IsDebugBuild>false</_IsDebugBuild> + <_IsDebugBuild Condition="'$(Configuration)' == 'Debug'">true</_IsDebugBuild> + <BlazorCacheBootResources Condition="'$(BlazorCacheBootResources)' == ''">true</BlazorCacheBootResources> + </PropertyGroup> + + <ItemGroup> + <_BlazorBootResource Include="@(_BlazorOutputWithTargetPath->HasMetadata('BootManifestResourceType'))" /> + </ItemGroup> + + <GenerateBlazorBootJson + AssemblyPath="@(IntermediateAssembly)" + Resources="@(_BlazorBootResource)" + DebugBuild="$(_IsDebugBuild)" + LinkerEnabled="$(BlazorWebAssemblyEnableLinking)" + CacheBootResources="$(BlazorCacheBootResources)" + OutputPath="$(_BlazorBootJsonIntermediateOutputPath)" + ConfigurationFiles="@(_BlazorConfigFile)" /> + + </Target> + + <Target Name="_GenerateBlazorBootJsonIntegrity"> + + <GetFileHash Files="$(_BlazorBootJsonIntermediateOutputPath)" Algorithm="SHA256" HashEncoding="base64"> + <Output TaskParameter="Items" ItemName="_BlazorBootJsonWithHash" /> + </GetFileHash> + + <ItemGroup> + + <_BlazorBootJsonWithIntegrity Include="@(_BlazorBootJsonWithHash)"> + <Integrity>%(FileHash)</Integrity> + <IntegrityFile>$(IntermediateOutputPath)integrity\$([System.String]::Copy('%(FileHash)').Replace('/','-').Replace('+','_')).hash</IntegrityFile> + </_BlazorBootJsonWithIntegrity> + + <_BlazorOutputWithTargetPath Include="@(_BlazorBootJsonWithIntegrity)" RemoveMetadata="FileHash;FileHashAlgorithm"> + <TargetOutputPath>$(_BaseBlazorRuntimeOutputPath)$(_BlazorBootJsonName)</TargetOutputPath> + </_BlazorOutputWithTargetPath> + + <FileWrites Include="$(_BlazorBootJsonIntermediateOutputPath)" /> + <FileWrites Include="%(_BlazorBootJsonWithIntegrity.IntegrityFile)" /> + + </ItemGroup> + + <WriteLinesToFile Lines="%(_BlazorBootJsonWithIntegrity.Integrity)" File="%(_BlazorBootJsonWithIntegrity.IntegrityFile)" WriteOnlyWhenDifferent="true" Overwrite="true" /> + + </Target> + +</Project> diff --git a/src/Components/Blazor/Build/src/targets/BuiltInBclLinkerDescriptor.xml b/src/Components/WebAssembly/Build/src/targets/BuiltInBclLinkerDescriptor.xml similarity index 59% rename from src/Components/Blazor/Build/src/targets/BuiltInBclLinkerDescriptor.xml rename to src/Components/WebAssembly/Build/src/targets/BuiltInBclLinkerDescriptor.xml index 3275831dca427bdfdab09195096aa102a398f1ff..4c955b737abecdf600bd61841bbaadb9bec59b11 100644 --- a/src/Components/Blazor/Build/src/targets/BuiltInBclLinkerDescriptor.xml +++ b/src/Components/WebAssembly/Build/src/targets/BuiltInBclLinkerDescriptor.xml @@ -19,9 +19,14 @@ <type fullname="System.ComponentModel.TimeSpanConverter" /> </assembly> - <assembly fullname="WebAssembly.Net.Http"> - <!-- Without this, the setter for DefaultCredentials would be removed, but we need it --> - <type fullname="WebAssembly.Net.Http.HttpClient.FetchCredentialsOption" /> - <type fullname="WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler" /> + <assembly fullname="System.Text.Json"> + <!-- S.T.J. uses Activator.CreateInstance to instantiate converters, so we need to preserve default constructors. + For safety, do this for all converter types, even though most of them are preserved anyway due to being referenced + statically. It's only JsonStringEnumConverter that currently isn't referenced statically. + The following is a workaround for https://github.com/dotnet/aspnetcore/issues/19086. + The underlying issue is reported in the runtime repo at https://github.com/dotnet/runtime/issues/34449 --> + <type fullname="System.Text.Json.Serialization.*Converter"> + <method signature="System.Void .ctor()" /> + </type> </assembly> </linker> diff --git a/src/Components/WebAssembly/Build/src/targets/CollationLinkerDescriptor.xml b/src/Components/WebAssembly/Build/src/targets/CollationLinkerDescriptor.xml new file mode 100644 index 0000000000000000000000000000000000000000..693bdb6768c1060bc9815603ceabb800455a47d1 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/targets/CollationLinkerDescriptor.xml @@ -0,0 +1,21 @@ +<linker> + <!-- This file disables exclusions that remove collation data --> + + <assembly fullname="mscorlib"> + <resource name="collation.cjkCHS.bin" action="remove"/> + <resource name="collation.cjkCHT.bin" action="remove"/> + <resource name="collation.cjkJA.bin" action="remove"/> + <resource name="collation.cjkKO.bin" action="remove"/> + <resource name="collation.cjkKOlv2.bin" action="remove"/> + <resource name="collation.core.bin" action="remove"/> + <resource name="collation.tailoring.bin" action="remove"/> + + <type fullname="System.Globalization.CompareInfo"> + <method signature="System.Boolean get_IgnoreCaseNotSupported()" body="stub" value="true"/> + </type> + + <type fullname="System.Globalization.CompareInfo"> + <method signature="System.Boolean get_UseManagedCollation()" body="stub" value="false"/> + </type> + </assembly> +</linker> \ No newline at end of file diff --git a/src/Components/WebAssembly/Build/src/targets/Compression.targets b/src/Components/WebAssembly/Build/src/targets/Compression.targets new file mode 100644 index 0000000000000000000000000000000000000000..348d62481a749b21fc400f5fa68d89e608e354f8 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/targets/Compression.targets @@ -0,0 +1,108 @@ +<Project> + <PropertyGroup> + + <_BlazorBrotliPath>$(_BlazorToolsDir)compression\blazor-brotli.dll</_BlazorBrotliPath> + <ResolveCurrentProjectStaticWebAssetsDependsOn> + $(ResolveCurrentProjectStaticWebAssetsDependsOn); + _ResolveBlazorFilesToCompress; + </ResolveCurrentProjectStaticWebAssetsDependsOn> + + </PropertyGroup> + + <Target Name="_ResolveBlazorFilesToCompress" AfterTargets="_ResolveBlazorGeneratedAssets"> + + <PropertyGroup> + <_BlazorFilesIntermediateOutputPath>$(IntermediateOutputPath)compressed\</_BlazorFilesIntermediateOutputPath> + <_GzipCompressionBlazorApplicationFilesManifestPath>$(IntermediateOutputPath)compressed\gzip.manifest.json</_GzipCompressionBlazorApplicationFilesManifestPath> + <_BrotliCompressionBlazorApplicationFilesManifestPath>$(IntermediateOutputPath)compressed\brotli.manifest.json</_BrotliCompressionBlazorApplicationFilesManifestPath> + </PropertyGroup> + + <MakeDir Directories="$(_BlazorFilesIntermediateOutputPath)" /> + <ItemGroup> + + <_CompressionCandidate Include="@(StaticWebAsset)" Condition="'%(SourceType)' == '' and $([System.String]::Copy('%(RelativePath)').Replace('\','/').StartsWith('_framework/'))" KeepDuplicates="false" /> + <_CompressionCandidateIntegrity Include="@(_BlazorOutputWithTargetPath->'%(FullPath)')" /> + <_CompressionCandidateWithIntegrity Include="%(Identity)"> + <SourceType>@(_CompressionCandidate->'%(SourceType)')</SourceType> + <SourceId>@(_CompressionCandidate->'%(SourceId)')</SourceId> + <ContentRoot>@(_CompressionCandidate->'%(ContentRoot)')</ContentRoot> + <BasePath>@(_CompressionCandidate->'%(BasePath)')</BasePath> + <RelativePath>@(_CompressionCandidate->'%(RelativePath)')</RelativePath> + <InputSource>@(_CompressionCandidateIntegrity->'%(IntegrityFile)')</InputSource> + </_CompressionCandidateWithIntegrity> + + <_GzipBlazorFileToCompress Include="@(_CompressionCandidateWithIntegrity)"> + <TargetCompressionPath>$(_BlazorFilesIntermediateOutputPath)%(RelativePath).gz</TargetCompressionPath> + <TargetOutputPath>%(RelativePath).gz</TargetOutputPath> + <RelativePath>%(RelativePath).gz</RelativePath> + </_GzipBlazorFileToCompress> + <_GzipBlazorFileToCompress Remove="@(_BlazorFileCompressExclusion)" /> + + <_BrotliBlazorFileToCompress Include="@(_CompressionCandidateWithIntegrity)"> + <TargetCompressionPath>$(_BlazorFilesIntermediateOutputPath)%(RelativePath).br</TargetCompressionPath> + <TargetOutputPath>%(RelativePath).br</TargetOutputPath> + <RelativePath>%(RelativePath).br</RelativePath> + </_BrotliBlazorFileToCompress> + <_BrotliBlazorFileToCompress Remove="@(_BlazorFileCompressExclusion)" /> + + <_BlazorFileToCompress Include="@(_GzipBlazorFileToCompress)" /> + <_BlazorFileToCompress Include="@(_BrotliBlazorFileToCompress)" /> + <_BlazorFileToCompress Remove="@(_BlazorFileCompressExclusion)" /> + + <_CompressedStaticWebAsset Include="@(_BlazorFileToCompress->'%(TargetCompressionPath)')" RemoveMetadata="TargetOutputPath;TargetCompressionPath" /> + + <StaticWebAsset Include="@(_CompressedStaticWebAsset->'%(FullPath)')" KeepMetadata="SourceType;SourceId;ContentRoot;BasePath;RelativePath" /> + <FileWrites Include="@(_CompressedStaticWebAsset)" /> + + </ItemGroup> + + </Target> + + <UsingTask TaskName="GzipCompressBlazorApplicationFiles" AssemblyFile="$(_BlazorTasksPath)" /> + <UsingTask TaskName="BrotliCompressBlazorApplicationFiles" AssemblyFile="$(_BlazorTasksPath)" /> + <UsingTask TaskName="GenerateBlazorCompressionManifest" AssemblyFile="$(_BlazorTasksPath)" /> + + <Target + Name="_GzipCompressBlazorApplicationFiles" + DependsOnTargets="ResolveStaticWebAssetsInputs" + BeforeTargets="_BlazorStaticWebAssetsCopyGeneratedFilesToOutputDirectory" + Inputs="$(_GzipCompressionBlazorApplicationFilesManifestPath)" + Outputs="@(_GzipBlazorFileToCompress->'%(TargetCompressionPath)')"> + + <GzipCompressBlazorApplicationFiles ManifestPath="$(_GzipCompressionBlazorApplicationFilesManifestPath)" /> + + </Target> + + <Target + Name="_GenerateGzipCompressionBlazorApplicationFilesManifest" + BeforeTargets="_GzipCompressBlazorApplicationFiles" + Inputs="@(_GzipBlazorFileToCompress->'%(InputSource)')" + Outputs="$(_GzipCompressionBlazorApplicationFilesManifestPath)"> + + <GenerateBlazorCompressionManifest FilesToCompress="@(_GzipBlazorFileToCompress)" ManifestPath="$(_GzipCompressionBlazorApplicationFilesManifestPath)" /> + </Target> + + <Target + Name="_BrotliCompressBlazorApplicationFiles" + BeforeTargets="GetCopyToPublishDirectoryItems;_CopyResolvedFilesToPublishPreserveNewest" + DependsOnTargets="ResolveStaticWebAssetsInputs" + Inputs="$(_BrotliCompressionBlazorApplicationFilesManifestPath)" + Outputs="@(_BrotliBlazorFileToCompress->'%(TargetCompressionPath)')"> + + <BrotliCompressBlazorApplicationFiles + BlazorBrotliPath="$(_BlazorBrotliPath)" + ManifestPath="$(_BrotliCompressionBlazorApplicationFilesManifestPath)" + ToolExe="$(_DotNetHostFileName)" + ToolPath="$(_DotNetHostDirectory)" /> + </Target> + + <Target + Name="_GenerateBrotliCompressionBlazorApplicationFilesManifest" + BeforeTargets="_GzipCompressBlazorApplicationFiles" + Inputs="@(_BrotliBlazorFileToCompress->'%(InputSource)')" + Outputs="$(_BrotliCompressionBlazorApplicationFilesManifestPath)"> + + <GenerateBlazorCompressionManifest FilesToCompress="@(_BrotliBlazorFileToCompress)" ManifestPath="$(_BrotliCompressionBlazorApplicationFilesManifestPath)" /> + </Target> + +</Project> diff --git a/src/Components/WebAssembly/Build/src/targets/Publish.targets b/src/Components/WebAssembly/Build/src/targets/Publish.targets new file mode 100644 index 0000000000000000000000000000000000000000..5847bb0aca524f37d8ecec54e2a994fa48f9e503 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/targets/Publish.targets @@ -0,0 +1,40 @@ +<Project> + <PropertyGroup> + <!-- Disable unwanted parts of the default publish process --> + <CopyBuildOutputToPublishDirectory>false</CopyBuildOutputToPublishDirectory> + <CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory> + <PreserveCompilationContext>false</PreserveCompilationContext> + <RazorCompileOnPublish>false</RazorCompileOnPublish> + <GenerateDependencyFile>false</GenerateDependencyFile> + <IsWebConfigTransformDisabled>true</IsWebConfigTransformDisabled> + </PropertyGroup> + + <Target + Name="_BlazorCleanupPublishOutput" + AfterTargets="ComputeResolvedFilesToPublishList" + Condition="'$(BlazorPrunePublishOutput)' != 'false'"> + + <ItemGroup> + <!-- Delete stray contents from the root of the the app. --> + <ResolvedFileToPublish + Remove="%(ResolvedFileToPublish.Identity)" + Condition="'%(ResolvedFileToPublish.RelativePath)' != 'web.config' AND !$([System.String]::Copy('%(ResolvedFileToPublish.RelativePath)').Replace('\','/').StartsWith('wwwroot/'))"/> + </ItemGroup> + </Target> + + <Target + Name="_BlazorCopyStandaloneWebConfig" + AfterTargets="_BlazorCleanupPublishOutput;ComputeResolvedFilesToPublishList" + Condition="'@(ResolvedFileToPublish->AnyHaveMetadataValue('RelativePath', 'web.config'))' != 'true'"> + + <ItemGroup> + <ResolvedFileToPublish Include="$(MSBuildThisFileDirectory)Standalone.Web.config"> + <ExcludeFromSingleFile>true</ExcludeFromSingleFile> + <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> + <RelativePath>web.config</RelativePath> + </ResolvedFileToPublish> + </ItemGroup> + + </Target> + +</Project> \ No newline at end of file diff --git a/src/Components/WebAssembly/Build/src/targets/ServiceWorkerAssetsManifest.targets b/src/Components/WebAssembly/Build/src/targets/ServiceWorkerAssetsManifest.targets new file mode 100644 index 0000000000000000000000000000000000000000..3a72b4e62a36c294a30e05a52f39f4b4d6273338 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/targets/ServiceWorkerAssetsManifest.targets @@ -0,0 +1,240 @@ +<Project> + + <Target Name="_ComputeServiceWorkerAssetsManifestInputs" + Condition="'$(ServiceWorkerAssetsManifest)' != ''" + BeforeTargets="_ResolveBlazorOutputs;_ResolveBlazorFilesToCompress"> + + <PropertyGroup> + <_ServiceWorkerAssetsManifestIntermediateOutputPath>$([MSBuild]::MakeRelative($(MSBuildProjectDirectory), $(_BlazorIntermediateOutputPath)))$(ServiceWorkerAssetsManifest)</_ServiceWorkerAssetsManifestIntermediateOutputPath> + <_ServiceWorkerAssetsManifestFullPath>$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)/$(_ServiceWorkerAssetsManifestIntermediateOutputPath)'))</_ServiceWorkerAssetsManifestFullPath> + </PropertyGroup> + + <ItemGroup> + <_BlazorOutputWithTargetPath + Include="$(_ServiceWorkerAssetsManifestFullPath)" + TargetOutputPath="$(_ServiceWorkerAssetsManifestIntermediateOutputPath)" /> + + <_ManifestStaticWebAsset Include="$(_ServiceWorkerAssetsManifestFullPath)"> + <SourceType></SourceType> + <SourceId>$(PackageId)</SourceId> + <ContentRoot>$([MSBuild]::NormalizeDirectory('$(TargetDir)wwwroot\'))</ContentRoot> + <BasePath>$(StaticWebAssetBasePath)</BasePath> + <RelativePath>$(ServiceWorkerAssetsManifest)</RelativePath> + </_ManifestStaticWebAsset> + + <StaticWebAsset Include="@(_ManifestStaticWebAsset)" /> + <_CompressionCandidate Include="@(_ManifestStaticWebAsset)" /> + <_CompressionCandidateWithIntegrity Include="@(_ManifestStaticWebAsset)"> + <InputSource>$(_ServiceWorkerAssetsManifestFullPath)</InputSource> + </_CompressionCandidateWithIntegrity> + + </ItemGroup> + + </Target> + + <UsingTask TaskName="GenerateServiceWorkerAssetsManifest" AssemblyFile="$(_BlazorTasksPath)" /> + + <Target Name="_WriteServiceWorkerAssetsManifest" + Condition="'$(ServiceWorkerAssetsManifest)' != ''" + Inputs="@(ServiceWorkerAssetsManifestItem)" + Outputs="$(_ServiceWorkerAssetsManifestIntermediateOutputPath)" + BeforeTargets="_ComputeManifestIntegrity" + DependsOnTargets="ResolveStaticWebAssetsInputs;_GenerateServiceWorkerIntermediateFiles"> + + <GenerateServiceWorkerAssetsManifest + Version="$(ServiceWorkerAssetsManifestVersion)" + AssetsWithHashes="@(_ServiceWorkerAssetsManifestItemWithHash)" + OutputPath="$(_ServiceWorkerAssetsManifestIntermediateOutputPath)" /> + + <ItemGroup> + <FileWrites Include="$(_ServiceWorkerAssetsManifestIntermediateOutputPath)" /> + </ItemGroup> + + </Target> + + <Target Name="_ComputeManifestIntegrity" + Condition="'$(ServiceWorkerAssetsManifest)' != ''" + BeforeTargets="_BlazorStaticWebAssetsCopyGeneratedFilesToOutputDirectory;_GzipCompressBlazorApplicationFiles"> + + <GetFileHash Files="$(_ServiceWorkerAssetsManifestIntermediateOutputPath)" Algorithm="SHA256" HashEncoding="base64"> + <Output TaskParameter="Items" ItemName="_ServiceWorkerManifestWithHash" /> + </GetFileHash> + + <ItemGroup> + + <_ServiceWorkerManifestWithIntegrity Include="@(_ServiceWorkerManifestWithHash)"> + <Integrity>%(FileHash)</Integrity> + <IntegrityFile>$(IntermediateOutputPath)integrity\$([System.String]::Copy('%(FileHash)').Replace('/','-').Replace('+','_')).hash</IntegrityFile> + </_ServiceWorkerManifestWithIntegrity> + + <FileWrites Include="%(_ServiceWorkerManifestWithIntegrity.IntegrityFile)" /> + + </ItemGroup> + + <WriteLinesToFile Lines="%(_ServiceWorkerManifestWithIntegrity.Integrity)" File="%(_ServiceWorkerManifestWithIntegrity.IntegrityFile)" WriteOnlyWhenDifferent="true" Overwrite="true" /> + + <PropertyGroup> + <_ServiceWorkerManifestIntegrityFile>$(IntermediateOutputPath)integrity\$([System.String]::Copy('%(_ServiceWorkerManifestWithIntegrity.FileHash)').Replace('/','-').Replace('+','_')).hash</_ServiceWorkerManifestIntegrityFile> + </PropertyGroup> + + <ItemGroup> + <_GzipFileToPatch Include="@(_GzipBlazorFileToCompress)" Condition="'%(Identity)' == '$(_ServiceWorkerAssetsManifestFullPath)'" KeepDuplicates="false"> + <InputSource>$(_ServiceWorkerManifestIntegrityFile)</InputSource> + </_GzipFileToPatch> + + <_GzipBlazorFileToCompress Remove="@(_GzipFileToPatch)" /> + <_GzipBlazorFileToCompress Include="@(_GzipFileToPatch)" /> + + <_BrotliFileToPatch Include="@(_BrotliBlazorFileToCompress)" Condition="'%(Identity)' == '$(_ServiceWorkerAssetsManifestFullPath)'" KeepDuplicates="false"> + <InputSource>$(_ServiceWorkerManifestIntegrityFile)</InputSource> + </_BrotliFileToPatch> + + <_BrotliBlazorFileToCompress Remove="@(_BrotliFileToPatch)" /> + <_BrotliBlazorFileToCompress Include="@(_BrotliFileToPatch)" /> + </ItemGroup> + + </Target> + + <Target Name="_ComputeServiceWorkerAssetsManifestFileHashes" + Condition="'$(ServiceWorkerAssetsManifest)' != ''" + DependsOnTargets="ResolveStaticWebAssetsInputs;_BlazorComputeOtherAssetsIntegrity"> + + <ItemGroup> + <ServiceWorkerAssetsManifestItem + Include="%(StaticWebAsset.Identity)" + Condition="'%(RelativePath)' != '$(ServiceWorkerAssetsManifest)'"> + <AssetUrl>$([System.String]::Copy('$([System.String]::Copy('%(StaticWebAsset.BasePath)').TrimEnd('/'))/%(StaticWebAsset.RelativePath)').Replace('\','/').TrimStart('/'))</AssetUrl> + </ServiceWorkerAssetsManifestItem> + + <!-- Don't include compressed files in the manifest, since their existence is transparent to the client --> + <ServiceWorkerAssetsManifestItem Remove="@(_CompressedStaticWebAsset->'%(FullPath)')" /> + + <!-- Don't include the service worker files in the manifest, as the service worker doesn't need to fetch itself --> + <ServiceWorkerAssetsManifestItem Remove="%(_ServiceWorkerIntermediateFile.FullPath)" /> + + <_ServiceWorkerExclude Include="@(_StaticWebAssetIntegrity)" /> + <_ServiceWorkerItemBase Include="@(ServiceWorkerAssetsManifestItem)" /> + <_ServiceWorkerItemBase Remove="@(_ServiceWorkerExclude)" /> + <_ServiceWorkerItemHash Include="@(ServiceWorkerAssetsManifestItem)" /> + <_ServiceWorkerItemHash Remove="@(_ServiceWorkerItemBase)" /> + <_ServiceWorkerAssetsManifestItemWithHash Include="%(Identity)"> + <AssetUrl>@(_ServiceWorkerItemHash->'%(AssetUrl)')</AssetUrl> + <Integrity>@(_StaticWebAssetIntegrity->'%(Integrity)')</Integrity> + </_ServiceWorkerAssetsManifestItemWithHash> + + </ItemGroup> + </Target> + + <Target Name="_BlazorComputeOtherAssetsIntegrity" Condition="'$(ServiceWorkerAssetsManifest)' != ''"> + <ItemGroup> + <_StaticWebAssetsWithoutHash Include="@(StaticWebAsset)" Condition="'%(SourceType)' != '' or '%(ContentRoot)' == '$(_BlazorCurrentProjectWWWroot)'" /> + <_StaticWebAssetsWithoutHash Remove="@(_StaticWebAssetIntegrity)" /> + </ItemGroup> + + <GetFileHash Files="@(_StaticWebAssetsWithoutHash)" Algorithm="SHA256" HashEncoding="base64"> + <Output TaskParameter="Items" ItemName="_StaticWebAssetHash" /> + </GetFileHash> + + <ItemGroup> + <_StaticWebAssetIntegrity Include="%(_StaticWebAssetHash.Identity)"> + <Integrity>%(_StaticWebAssetHash.FileHash)</Integrity> + </_StaticWebAssetIntegrity> + </ItemGroup> + + </Target> + + + <!-- + Compute a default ServiceWorkerAssetsManifestVersion value by combining all the asset hashes. + This is useful because then clients will only have to repopulate caches if the contents have changed. + --> + <Target Name="_ComputeDefaultServiceWorkerAssetsManifestVersion" + Condition="'$(ServiceWorkerAssetsManifest)' != ''" + DependsOnTargets="_ComputeServiceWorkerAssetsManifestFileHashes"> + <PropertyGroup> + <_CombinedHashIntermediatePath>$(_BlazorIntermediateOutputPath)serviceworkerhashes.txt</_CombinedHashIntermediatePath> + </PropertyGroup> + + <!-- Neither of these should ever happen, but if we do we want to know about it. --> + <Error Text="Cannot compute service worker assets manifest version, because no service worker manifest items were defined." + Condition="'@(_ServiceWorkerAssetsManifestItemWithHash)' == ''" /> + <Error Text="While computing service worker assets manifest version, did not find any dll entries in service worker assets manifest." + Condition="'@(_ServiceWorkerAssetsManifestItemWithHash->WithMetadataValue('Extension', '.dll'))' == ''" /> + + <WriteLinesToFile + File="$(_CombinedHashIntermediatePath)" + Lines="@(_ServiceWorkerAssetsManifestItemWithHash->'%(FileHash)')" + WriteOnlyWhenDifferent="true" + Overwrite="true" /> + + <GetFileHash Files="$(_CombinedHashIntermediatePath)" Algorithm="SHA256" HashEncoding="base64"> + <Output TaskParameter="Items" ItemName="_ServiceWorkerAssetsManifestCombinedHash" /> + </GetFileHash> + + <PropertyGroup> + <ServiceWorkerAssetsManifestVersion Condition="'$(ServiceWorkerAssetsManifestVersion)' == ''">$([System.String]::Copy('%(_ServiceWorkerAssetsManifestCombinedHash.FileHash)').Substring(0, 8))</ServiceWorkerAssetsManifestVersion> + </PropertyGroup> + </Target> + + <Target Name="_OmitServiceWorkerContent" + Condition="'$(ServiceWorkerAssetsManifest)' != ''" + BeforeTargets="AssignTargetPaths;ResolveCurrentProjectStaticWebAssetsInputs"> + + <ItemGroup> + <!-- Don't emit the service worker source files to the output --> + <Content Remove="@(ServiceWorker)" /> + <Content Remove="@(ServiceWorker->'%(PublishedContent)')" /> + </ItemGroup> + </Target> + + <Target Name="_ResolveServiceWorkerOutputs" + Condition="'$(ServiceWorkerAssetsManifest)' != ''" + BeforeTargets="_ResolveBlazorOutputs" + DependsOnTargets="_ComputeServiceWorkerOutputs"> + + <ItemGroup> + <_BlazorFileCompressExclusion Include="@(_ServiceWorkerIntermediateFile->'%(FullPath)')" /> + + <_ServiceWorkerStaticWebAsset Include="@(_ServiceWorkerIntermediateFile->'%(FullPath)')"> + <SourceType></SourceType> + <SourceId>$(PackageId)</SourceId> + <ContentRoot>$([MSBuild]::NormalizeDirectory('$(TargetDir)wwwroot\'))</ContentRoot> + <BasePath>$(StaticWebAssetBasePath)</BasePath> + <RelativePath>%(TargetOutputPath)</RelativePath> + </_ServiceWorkerStaticWebAsset> + + <StaticWebAsset Include="@(_ServiceWorkerStaticWebAsset)" /> + + </ItemGroup> + </Target> + + <Target Name="_ComputeServiceWorkerOutputs" + Condition="'$(ServiceWorkerAssetsManifest)' != ''"> + + <ItemGroup> + <!-- Figure out where we're getting the content for each @(ServiceWorker) entry, depending on whether there's a PublishedContent value --> + <_ServiceWorkerIntermediateFile Include="@(ServiceWorker->'$(IntermediateOutputPath)blazor\serviceworkers\%(Identity)')"> + <ContentSourcePath Condition="'%(ServiceWorker.PublishedContent)' != ''">%(ServiceWorker.PublishedContent)</ContentSourcePath> + <ContentSourcePath Condition="'%(ServiceWorker.PublishedContent)' == ''">%(ServiceWorker.Identity)</ContentSourcePath> + <TargetOutputPath>%(ServiceWorker.Identity)</TargetOutputPath> + <TargetOutputPath Condition="$([System.String]::Copy('%(ServiceWorker.Identity)').Replace('\','/').StartsWith('wwwroot/'))">$([System.String]::Copy('%(ServiceWorker.Identity)').Substring(8))</TargetOutputPath> + </_ServiceWorkerIntermediateFile> + </ItemGroup> + </Target> + + <Target Name="_GenerateServiceWorkerIntermediateFiles" + Condition="'$(ServiceWorkerAssetsManifest)' != ''" + Inputs="@(_ServiceWorkerIntermediateFile->'%(ContentSourcePath)'); $(_CombinedHashIntermediatePath)" + Outputs="@(_ServiceWorkerIntermediateFile)" + DependsOnTargets="_ComputeDefaultServiceWorkerAssetsManifestVersion"> + <Copy SourceFiles="%(_ServiceWorkerIntermediateFile.ContentSourcePath)" DestinationFiles="%(_ServiceWorkerIntermediateFile.Identity)" /> + <WriteLinesToFile + File="%(_ServiceWorkerIntermediateFile.Identity)" + Lines="/* Manifest version: $(ServiceWorkerAssetsManifestVersion) */" + Condition="'$(ServiceWorkerAssetsManifestVersion)' != ''" /> + <ItemGroup> + <FileWrites Include="%(_ServiceWorkerIntermediateFile.Identity)" /> + </ItemGroup> + </Target> + +</Project> diff --git a/src/Components/Blazor/Build/src/targets/Standalone.Web.config b/src/Components/WebAssembly/Build/src/targets/Standalone.Web.config similarity index 85% rename from src/Components/Blazor/Build/src/targets/Standalone.Web.config rename to src/Components/WebAssembly/Build/src/targets/Standalone.Web.config index 177a9b14046adc20013289f2674afe4b7dc8ce2a..7f9995d792c3df47a6b92c4d598e44a2715be78c 100644 --- a/src/Components/Blazor/Build/src/targets/Standalone.Web.config +++ b/src/Components/WebAssembly/Build/src/targets/Standalone.Web.config @@ -2,16 +2,18 @@ <configuration> <system.webServer> <staticContent> + <remove fileExtension=".dat" /> <remove fileExtension=".dll" /> <remove fileExtension=".json" /> <remove fileExtension=".wasm" /> <remove fileExtension=".woff" /> <remove fileExtension=".woff2" /> <mimeMap fileExtension=".dll" mimeType="application/octet-stream" /> + <mimeMap fileExtension=".dat" mimeType="application/octet-stream" /> <mimeMap fileExtension=".json" mimeType="application/json" /> <mimeMap fileExtension=".wasm" mimeType="application/wasm" /> <mimeMap fileExtension=".woff" mimeType="application/font-woff" /> - <mimeMap fileExtension=".woff2" mimeType="application/font-woff" /> + <mimeMap fileExtension=".woff2" mimeType="application/font-woff" /> </staticContent> <httpCompression> <dynamicTypes> @@ -23,14 +25,14 @@ <rules> <rule name="Serve subdir"> <match url=".*" /> - <action type="Rewrite" url="[ServeSubdirectory]{R:0}" /> + <action type="Rewrite" url="wwwroot\{R:0}" /> </rule> <rule name="SPA fallback routing" stopProcessing="true"> <match url=".*" /> <conditions logicalGrouping="MatchAll"> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> </conditions> - <action type="Rewrite" url="[ServeSubdirectory]" /> + <action type="Rewrite" url="wwwroot\" /> </rule> </rules> </rewrite> diff --git a/src/Components/WebAssembly/Build/src/targets/StaticWebAssets.props b/src/Components/WebAssembly/Build/src/targets/StaticWebAssets.props new file mode 100644 index 0000000000000000000000000000000000000000..d15d07d7291a3ecbf9f4a89cb7ffe10514053124 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/targets/StaticWebAssets.props @@ -0,0 +1,15 @@ +<Project> + <PropertyGroup> + <ResolveStaticWebAssetsInputsDependsOn> + $(ResolveStaticWebAssetsInputsDependsOn); + _BlazorApplyLinkPreferencesToStaticWebAssets; + _ResolveBlazorGeneratedAssets; + </ResolveStaticWebAssetsInputsDependsOn> + + <GetCurrentProjectStaticWebAssetsDependsOn> + $(GetCurrentProjectStaticWebAssetsDependsOn); + _BlazorApplyLinkPreferencesToStaticWebAssets; + _ResolveBlazorGeneratedAssets; + </GetCurrentProjectStaticWebAssetsDependsOn> + </PropertyGroup> +</Project> diff --git a/src/Components/WebAssembly/Build/src/targets/StaticWebAssets.targets b/src/Components/WebAssembly/Build/src/targets/StaticWebAssets.targets new file mode 100644 index 0000000000000000000000000000000000000000..9c16d9dd5a8158b1db38db805a26c92a2e291779 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/targets/StaticWebAssets.targets @@ -0,0 +1,165 @@ +<Project> + + <PropertyGroup> + <_BlazorCurrentProjectWWWroot>$([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)\wwwroot\'))</_BlazorCurrentProjectWWWroot> + </PropertyGroup> + + <Target Name="_ResolveBlazorGeneratedAssets" DependsOnTargets="_PrepareBlazorOutputs"> + <ItemGroup> + <_BlazorOutputCandidateAsset Include="@(_BlazorOutputWithTargetPath->'%(FullPath)')"> + <SourceType></SourceType> + <SourceId>$(PackageId)</SourceId> + <ContentRoot>$([MSBuild]::NormalizeDirectory('$(TargetDir)wwwroot\'))</ContentRoot> + <BasePath>$(StaticWebAssetBasePath)</BasePath> + <RelativePath>$([System.String]::Copy('%(_BlazorOutputWithTargetPath.TargetOutputPath)').Replace('\','/'))</RelativePath> + <Integrity>%(_BlazorOutputWithTargetPath.Integrity)</Integrity> + </_BlazorOutputCandidateAsset> + + <_BlazorOutputCandidateAsset Remove="@(StaticWebAsset)" /> + + <_StaticWebAssetIntegrity Include="@(_BlazorOutputCandidateAsset)" KeepMetadata="Integrity" /> + + <StaticWebAsset Include="@(_BlazorOutputCandidateAsset)" KeepMetadata="SourceType;SourceId;ContentRoot;BasePath;RelativePath" /> + + <StaticWebAsset Remove="@(StaticWebAsset)" Condition="'$(BlazorEnableDebugging)' != 'true' and '%(SourceType)' == '' and '%(Extension)' == '.pdb'" /> + + <!-- We are depending on a "private" property for static web assets, but this is something we can clean-up in a later release. + These files are not "external" in the "traditional" sense but it is fine for now as this is an implementation detail. + We only need to do this for the standalone case, for hosted scenarios this works just fine as the assets are considered + external. --> + <_ExternalStaticWebAsset Include="@(_BlazorOutputWithTargetPath->'%(FullPath)')"> + <SourceId>$(PackageId)</SourceId> + <!-- We just do this to keep the existing implementation happy. We will update this in the next release. --> + <SourceType>Generated</SourceType> + <ContentRoot>$([MSBuild]::NormalizeDirectory('$(TargetDir)wwwroot\'))</ContentRoot> + <BasePath>$(StaticWebAssetBasePath)</BasePath> + </_ExternalStaticWebAsset> + + <!-- These items we are adding for forward-compatibility with newer SDK versions. The paths listed here will be added unconditionally + to the generated static web assets manifest. This is only needed for forward compatibility in Blazor standalone scenarios. --> + <StaticWebAssetsManifestPath Include="$([MSBuild]::NormalizeDirectory('$(TargetDir)wwwroot\'))"> + <SourceId>$(PackageId)</SourceId> + <BasePath>$(StaticWebAssetBasePath)</BasePath> + </StaticWebAssetsManifestPath> + </ItemGroup> + + </Target> + + <Target Name="_BlazorStaticWebAssetsCopyGeneratedFilesToOutputDirectory" + DependsOnTargets="ResolveStaticWebAssetsInputs;$(_BlazorCopyFilesToOutputDirectoryDependsOn)" + AfterTargets="CopyFilesToOutputDirectory" + Condition="'$(OutputType.ToLowerInvariant())'=='exe'"> + + <ItemGroup> + <_BlazorCopyLocalAssets + Include="@(StaticWebAsset)" + Condition="'%(SourceType)' == '' and '%(ContentRoot)' != '$(_BlazorCurrentProjectWWWroot)' and !$([System.String]::Copy('%(RelativePath)').EndsWith('.br'))" /> + <_BlazorCopyLocalAssets Remove="@(_BlazorCopyLocalExclusion)" /> + </ItemGroup> + + <!-- Copy the blazor output files --> + <Copy + SourceFiles="@(_BlazorCopyLocalAssets)" + DestinationFiles="@(_BlazorCopyLocalAssets->'%(ContentRoot)%(RelativePath)')" + SkipUnchangedFiles="$(SkipCopyUnchangedFiles)" + OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)" + Retries="$(CopyRetryCount)" + RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" + UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)" + UseSymboliclinksIfPossible="$(CreateSymbolicLinksForCopyFilesToOutputDirectoryIfPossible)" + Condition="'@(_BlazorCopyLocalAssets)' != '' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'"> + </Copy> + + <ItemGroup> + <FileWrites Include="@(_BlazorCopyLocalAssets->'%(ContentRoot)%(RelativePath)')" /> + </ItemGroup> + + <ItemGroup> + <_BlazorStatisticsOutput Include="@(_BlazorCopyLocalAssets->'%(RelativePath)')" /> + </ItemGroup> + + <Message Importance="high" Text="$(TargetName) (Blazor output) -> $(TargetDir)wwwroot" /> + </Target> + + <Target Name="_StaticWebAssetsBlazorStandalonePublish" + AfterTargets="_StaticWebAssetsComputeFilesToPublish"> + + <ItemGroup> + + <_CurrentProjectStandalonePublishStaticWebAsset Include="%(StaticWebAsset.FullPath)" Condition="'%(StaticWebAsset.SourceType)' == ''"> + <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> + <RelativePath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)','$([MSBuild]::NormalizePath('wwwroot\%(BasePath)\%(RelativePath)'))'))</RelativePath> + </_CurrentProjectStandalonePublishStaticWebAsset> + + <!-- Remove any existing external static web asset that might have been added as part of the + regular publish pipeline. --> + <ResolvedFileToPublish Remove="@(_CurrentProjectStandalonePublishStaticWebAsset)" /> + + <ResolvedFileToPublish Include="@(_CurrentProjectStandalonePublishStaticWebAsset)"> + <ExcludeFromSingleFile>true</ExcludeFromSingleFile> + </ResolvedFileToPublish> + + </ItemGroup> + + </Target> + + <Target Name="_BlazorApplyLinkPreferencesToStaticWebAssets"> + <ItemGroup> + <_ContentWithWwrootLinkAttribute Include="@(Content)" Condition="'%(Content.Link)' != '' and $([System.String]::Copy('%(Content.Link)').Replace('\','/').StartsWith('wwwroot/'))" /> + + <_ContentLinkedIntoWwwroot + Include="@(_ContentWithWwrootLinkAttribute)" + Condition="@(_ContentWithWwrootLinkAttribute) != '' and '%(_ContentWithWwrootLinkAttribute.CopyToPublishDirectory)' != 'false'"> + <!-- This gets rid of wwwroot\ --> + <RelativePath>$([System.String]::Copy('%(Link)').Substring(8))</RelativePath> + </_ContentLinkedIntoWwwroot> + + <_OutsideContentLinkedIntoWwwroot Include="@(_ContentLinkedIntoWwwroot->'%(FullPath)')" Condition="@(_ContentLinkedIntoWwwroot) != '' and !$([System.String]::Copy('%(Identity)').Replace('\','/').StartsWith('wwwroot/'))" /> + <_WwwrootLinkedContent Include="@(_ContentLinkedIntoWwwroot->'%(FullPath)')" Condition="@(_ContentLinkedIntoWwwroot) != '' and $([System.String]::Copy('%(Identity)').Replace('\','/').StartsWith('wwwroot/'))" KeepMetadata="RelativePath" /> + + <!-- For content items with the Link attribute on them, we update the relative path. This enables support at publish time for these assets but forgoes any + dev-time support. (If you want to have dev-time support, you need to add an appropriate file into the wwwroot folder with CopyToPublishDirectory="false") + --> + <_NonLinkedStaticWebAssets Include="@(StaticWebAsset)" Exclude="@(_WwwrootLinkedContent)" /> + <_LinkedStaticWebAssets Include="@(StaticWebAsset)" Exclude="@(_NonLinkedStaticWebAssets)" /> + + <_UpdatedStaticWebAssets Include="%(Identity)"> + <SourceType>@(_LinkedStaticWebAssets->'%(SourceType)')</SourceType> + <SourceId>@(_LinkedStaticWebAssets->'%(SourceId)')</SourceId> + <ContentRoot>@(_LinkedStaticWebAssets->'%(ContentRoot)')</ContentRoot> + <BasePath>@(_LinkedStaticWebAssets->'%(BasePath)')</BasePath> + <RelativePath>@(_WwwrootLinkedContent->'%(RelativePath)')</RelativePath> + </_UpdatedStaticWebAssets> + + <StaticWebAsset Remove="@(_UpdatedStaticWebAssets)" /> + <StaticWebAsset Include="@(_UpdatedStaticWebAssets)" /> + + <!-- This allows limited publish time support for content items that are outside the wwwroot folder but are linked into the wwwroot folder. For example: + * Imagine a set of items in <ProjectDir>/Client/publish that are linked into <ProjectDir>/wwwroot/. + * We will consider them static web assets and default their content root to `<ProjectDir>/wwwroot` and their relative path will be whatever is after wwwroot\ + * We don't guarantee that these assets can be resolved at development time. + * We do guarantee that we will copy them into the actual wwwroot folder at publish time. + * If you want this type of dev-time support, the recomendation is to add the list of assets to the item group manually indicating a suitable content root. + --> + <StaticWebAsset Include="@(_OutsideContentLinkedIntoWwwroot)" Condition="@(_OutsideContentLinkedIntoWwwroot) != ''"> + <SourceType></SourceType> + <SourceId>$(PackageId)</SourceId> + <!-- We don't try to come up with a separate content root to make the inner loop work. + You can add items with CopyToPublishDirectory=false for development time support or add the assets directly by pasing in the parameters + --> + <ContentRoot>$([MSBuild]::NormalizeDirectory('$(MSBuildProjectDirectory)\wwwroot\'))</ContentRoot> + <BasePath>$(StaticWebAssetBasePath)</BasePath> + <RelativePath>%(_OutsideContentLinkedIntoWwwroot.RelativePath)</RelativePath> + </StaticWebAsset> + + <_StaticWebAssetsPublishFalse Include="@(Content->'%(FullPath)')" Condition="'%(Content.CopyToPublishDirectory)' == 'false'" /> + <_StaticWebAssetsLinkOutsideWwwroot Include="@(Content->'%(FullPath)')" Condition="'%(Content.Link)' != '' and !$([System.String]::Copy('%(Content.Link)').Replace('\','/').StartsWith('wwwroot/'))" /> + + <!-- Remove files that wouldn't be copied to the publish folder or that would be copied outside of the wwwroot folder if they were ever considered static web assets --> + <StaticWebAsset Remove="@(_StaticWebAssetsPublishFalse)" /> + <StaticWebAsset Remove="@(_StaticWebAssetsLinkOutsideWwwroot)" /> + </ItemGroup> + + </Target> + +</Project> diff --git a/src/Components/Blazor/Build/test/BlazorCreateRootDescriptorFileTest.cs b/src/Components/WebAssembly/Build/test/BlazorCreateRootDescriptorFileTest.cs similarity index 95% rename from src/Components/Blazor/Build/test/BlazorCreateRootDescriptorFileTest.cs rename to src/Components/WebAssembly/Build/test/BlazorCreateRootDescriptorFileTest.cs index 4470546cf07156cc1cf248b7f953c334b03f0a1a..2ad0ecdddda205c485e78c06559b8b99ad817d74 100644 --- a/src/Components/Blazor/Build/test/BlazorCreateRootDescriptorFileTest.cs +++ b/src/Components/WebAssembly/Build/test/BlazorCreateRootDescriptorFileTest.cs @@ -5,7 +5,7 @@ using System.IO; using System.Xml.Linq; using Xunit; -namespace Microsoft.AspNetCore.Blazor.Build +namespace Microsoft.AspNetCore.Components.WebAssembly.Build { public class BlazorCreateRootDescriptorFileTest { diff --git a/src/Components/WebAssembly/Build/test/BlazorReadSatelliteAssemblyFileTest.cs b/src/Components/WebAssembly/Build/test/BlazorReadSatelliteAssemblyFileTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..991d11f7da6dcc0f49d86459039f991835332b74 --- /dev/null +++ b/src/Components/WebAssembly/Build/test/BlazorReadSatelliteAssemblyFileTest.cs @@ -0,0 +1,69 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.IO; +using System.Xml.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build +{ + public class BlazorReadSatelliteAssemblyFileTest + { + [Fact] + public void WritesAndReadsRoundTrip() + { + // Arrange/Act + var tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + var writer = new BlazorWriteSatelliteAssemblyFile + { + BuildEngine = Mock.Of<IBuildEngine>(), + WriteFile = new TaskItem(tempFile), + SatelliteAssembly = new[] + { + new TaskItem("Resources.fr.dll", new Dictionary<string, string> + { + ["Culture"] = "fr", + ["DestinationSubDirectory"] = "fr\\", + }), + new TaskItem("Resources.ja-jp.dll", new Dictionary<string, string> + { + ["Culture"] = "ja-jp", + ["DestinationSubDirectory"] = "ja-jp\\", + }), + }, + }; + + var reader = new BlazorReadSatelliteAssemblyFile + { + BuildEngine = Mock.Of<IBuildEngine>(), + ReadFile = new TaskItem(tempFile), + }; + + writer.Execute(); + + Assert.True(File.Exists(tempFile), "Write should have succeeded."); + + reader.Execute(); + + Assert.Collection( + reader.SatelliteAssembly, + assembly => + { + Assert.Equal("Resources.fr.dll", assembly.ItemSpec); + Assert.Equal("fr", assembly.GetMetadata("Culture")); + Assert.Equal("fr\\", assembly.GetMetadata("DestinationSubDirectory")); + }, + assembly => + { + Assert.Equal("Resources.ja-jp.dll", assembly.ItemSpec); + Assert.Equal("ja-jp", assembly.GetMetadata("Culture")); + Assert.Equal("ja-jp\\", assembly.GetMetadata("DestinationSubDirectory")); + }); + } + } +} diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/Assert.cs b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/Assert.cs similarity index 92% rename from src/Components/Blazor/Build/test/BuildIntegrationTests/Assert.cs rename to src/Components/WebAssembly/Build/test/BuildIntegrationTests/Assert.cs index 8d0aa4b6dab19de7f385c020ccaa74c40325adb1..aaf3e508b56fa2cd124565e31df5be09a6fc318b 100644 --- a/src/Components/Blazor/Build/test/BuildIntegrationTests/Assert.cs +++ b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/Assert.cs @@ -9,10 +9,11 @@ using System.IO.Compression; using System.Linq; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; -namespace Microsoft.AspNetCore.Blazor.Build +namespace Microsoft.AspNetCore.Components.WebAssembly.Build { internal class Assert : Xunit.Assert { @@ -238,6 +239,23 @@ namespace Microsoft.AspNetCore.Blazor.Build } } + public static void FileHashEquals(MSBuildResult result, string filePath, string expectedSha256Base64) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + filePath = Path.Combine(result.Project.DirectoryPath, filePath); + FileExists(result, filePath); + + var actual = File.ReadAllBytes(filePath); + using var algorithm = SHA256.Create(); + var actualSha256 = algorithm.ComputeHash(actual); + var actualSha256Base64 = Convert.ToBase64String(actualSha256); + Assert.Equal(expectedSha256Base64, actualSha256Base64); + } + public static void FileEquals(MSBuildResult result, string expected, string actual) { if (result == null) @@ -316,7 +334,7 @@ namespace Microsoft.AspNetCore.Blazor.Build return filePath; } - public static void FileCountEquals(MSBuildResult result, int expected, string directoryPath, string searchPattern) + public static void FileCountEquals(MSBuildResult result, int expected, string directoryPath, string searchPattern, SearchOption searchOption = SearchOption.AllDirectories) { if (result == null) { @@ -337,7 +355,7 @@ namespace Microsoft.AspNetCore.Blazor.Build if (Directory.Exists(directoryPath)) { - var files = Directory.GetFiles(directoryPath, searchPattern, SearchOption.AllDirectories); + var files = Directory.GetFiles(directoryPath, searchPattern, searchOption); if (files.Length != expected) { throw new FileCountException(result, expected, directoryPath, searchPattern, files); @@ -516,7 +534,7 @@ namespace Microsoft.AspNetCore.Blazor.Build { using (var file = File.OpenRead(assemblyPath)) { - var peReader = new PEReader(file); + using var peReader = new PEReader(file); var metadataReader = peReader.GetMetadataReader(); return metadataReader.TypeDefinitions.Where(t => !t.IsNil).Select(t => { @@ -526,6 +544,44 @@ namespace Microsoft.AspNetCore.Blazor.Build } } + public static void AssemblyContainsResource(MSBuildResult result, string assemblyPath, string resourceName) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + assemblyPath = Path.Combine(result.Project.DirectoryPath, Path.Combine(assemblyPath)); + + var resources = GetAssemblyResourceNames(assemblyPath); + Assert.Contains(resourceName, resources); + } + + public static void AssemblyDoesNotContainResource(MSBuildResult result, string assemblyPath, string resourceName) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + assemblyPath = Path.Combine(result.Project.DirectoryPath, Path.Combine(assemblyPath)); + + var resources = GetAssemblyResourceNames(assemblyPath); + Assert.DoesNotContain(resourceName, resources); + } + + private static IEnumerable<string> GetAssemblyResourceNames(string assemblyPath) + { + using var file = File.OpenRead(assemblyPath); + using var peReader = new PEReader(file); + var metadataReader = peReader.GetMetadataReader(); + return metadataReader.ManifestResources.Where(r => !r.IsNil).Select(r => + { + var resource = metadataReader.GetManifestResource(r); + return metadataReader.GetString(resource.Name); + }).ToArray(); + } + public static void AssemblyHasAttribute(MSBuildResult result, string assemblyPath, string fullTypeName) { if (result == null) diff --git a/src/Components/WebAssembly/Build/test/BuildIntegrationTests/BuildCompressionTests.cs b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/BuildCompressionTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..476035b503932b2c07e3da0115dd5f576d705c65 --- /dev/null +++ b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/BuildCompressionTests.cs @@ -0,0 +1,396 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build +{ + public class BuildCompressionTests + { + [Fact] + public async Task Build_WithLinkerAndCompression_IsIncremental() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + // Act + var compressedFilesFolder = Path.Combine(project.IntermediateOutputDirectory, "compressed"); + var thumbPrint = FileThumbPrint.CreateFolderThumbprint(project, compressedFilesFolder); + + // Assert + for (var i = 0; i < 3; i++) + { + result = await MSBuildProcessManager.DotnetMSBuild(project); + Assert.BuildPassed(result); + + var newThumbPrint = FileThumbPrint.CreateFolderThumbprint(project, compressedFilesFolder); + Assert.Equal(thumbPrint.Count, newThumbPrint.Count); + for (var j = 0; j < thumbPrint.Count; j++) + { + Assert.Equal(thumbPrint[j], newThumbPrint[j]); + } + } + } + + [Fact] + public async Task Build_WithLinkerAndCompression_UpdatesFilesWhenSourcesChange() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var mainAppDll = Path.Combine(project.DirectoryPath, project.BuildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll"); + var mainAppDllThumbPrint = FileThumbPrint.Create(mainAppDll); + var mainAppCompressedDll = Path.Combine(project.DirectoryPath, project.BuildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll.gz"); + var mainAppCompressedDllThumbPrint = FileThumbPrint.Create(mainAppCompressedDll); + + var blazorBootJson = Path.Combine(project.DirectoryPath, project.BuildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); + var blazorBootJsonThumbPrint = FileThumbPrint.Create(blazorBootJson); + var blazorBootJsonCompressed = Path.Combine(project.DirectoryPath, project.BuildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json.gz"); + var blazorBootJsonCompressedThumbPrint = FileThumbPrint.Create(blazorBootJsonCompressed); + + // Act + var programFile = Path.Combine(project.DirectoryPath, "Program.cs"); + var programFileContents = File.ReadAllText(programFile); + File.WriteAllText(programFile, programFileContents.Replace("args", "arguments")); + result = await MSBuildProcessManager.DotnetMSBuild(project); + + // Assert + Assert.BuildPassed(result); + var newMainAppDllThumbPrint = FileThumbPrint.Create(mainAppDll); + var newMainAppCompressedDllThumbPrint = FileThumbPrint.Create(mainAppCompressedDll); + var newBlazorBootJsonThumbPrint = FileThumbPrint.Create(blazorBootJson); + var newBlazorBootJsonCompressedThumbPrint = FileThumbPrint.Create(blazorBootJsonCompressed); + + Assert.NotEqual(mainAppDllThumbPrint, newMainAppDllThumbPrint); + Assert.NotEqual(mainAppCompressedDllThumbPrint, newMainAppCompressedDllThumbPrint); + + Assert.NotEqual(blazorBootJsonThumbPrint, newBlazorBootJsonThumbPrint); + Assert.NotEqual(blazorBootJsonCompressedThumbPrint, newBlazorBootJsonCompressedThumbPrint); + } + + [Fact] + public async Task Build_WithoutLinkerAndCompression_IsIncremental() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/p:BlazorWebAssemblyEnableLinking=false"); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + // Act + var compressedFilesFolder = Path.Combine(project.IntermediateOutputDirectory, "compressed"); + var thumbPrint = FileThumbPrint.CreateFolderThumbprint(project, compressedFilesFolder); + + // Assert + for (var i = 0; i < 3; i++) + { + result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/p:BlazorWebAssemblyEnableLinking=false"); + Assert.BuildPassed(result); + + var newThumbPrint = FileThumbPrint.CreateFolderThumbprint(project, compressedFilesFolder); + Assert.Equal(thumbPrint.Count, newThumbPrint.Count); + for (var j = 0; j < thumbPrint.Count; j++) + { + Assert.Equal(thumbPrint[j], newThumbPrint[j]); + } + } + } + + [Fact] + public async Task Build_WithoutLinkerAndCompression_UpdatesFilesWhenSourcesChange() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/p:BlazorWebAssemblyEnableLinking=false"); + + // Act + var mainAppDll = Path.Combine(project.DirectoryPath, project.BuildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll"); + var mainAppDllThumbPrint = FileThumbPrint.Create(mainAppDll); + + var mainAppCompressedDll = Path.Combine(project.DirectoryPath, project.BuildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll.gz"); + var mainAppCompressedDllThumbPrint = FileThumbPrint.Create(mainAppCompressedDll); + + var programFile = Path.Combine(project.DirectoryPath, "Program.cs"); + var programFileContents = File.ReadAllText(programFile); + File.WriteAllText(programFile, programFileContents.Replace("args", "arguments")); + + // Assert + result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/p:BlazorWebAssemblyEnableLinking=false"); + Assert.BuildPassed(result); + var newMainAppDllThumbPrint = FileThumbPrint.Create(mainAppDll); + var newMainAppCompressedDllThumbPrint = FileThumbPrint.Create(mainAppCompressedDll); + + Assert.NotEqual(mainAppDllThumbPrint, newMainAppDllThumbPrint); + Assert.NotEqual(mainAppCompressedDllThumbPrint, newMainAppCompressedDllThumbPrint); + } + + [Fact] + public async Task Build_CompressesAllFrameworkFiles() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + var extensions = new[] { ".dll", ".js", ".pdb", ".wasm", ".map", ".json", ".dat" }; + // Act + var compressedFilesPath = Path.Combine( + project.DirectoryPath, + project.IntermediateOutputDirectory, + "compressed", + "_framework"); + var compressedFiles = Directory.EnumerateFiles( + compressedFilesPath, + "*", + SearchOption.AllDirectories) + .Where(f => Path.GetExtension(f) == ".gz") + .Select(f => Path.GetRelativePath(compressedFilesPath, f[0..^3])) + .OrderBy(f => f) + .ToArray(); + + var frameworkFilesPath = Path.Combine( + project.DirectoryPath, + project.BuildOutputDirectory, + "wwwroot", + "_framework"); + var frameworkFiles = Directory.EnumerateFiles( + frameworkFilesPath, + "*", + SearchOption.AllDirectories) + .Where(f => extensions.Contains(Path.GetExtension(f))) + .Select(f => Path.GetRelativePath(frameworkFilesPath, f)) + .OrderBy(f => f) + .ToArray(); + + Assert.Equal(frameworkFiles.Length, compressedFiles.Length); + Assert.Equal(frameworkFiles, compressedFiles); + + var brotliFiles = Directory.EnumerateFiles( + compressedFilesPath, + "*", + SearchOption.AllDirectories) + .Where(f => Path.GetExtension(f) == ".br") + .Select(f => Path.GetRelativePath(compressedFilesPath, f[0..^3])) + .OrderBy(f => f) + .ToArray(); + + // We don't compress things with brotli at build time + Assert.Empty(brotliFiles); + } + + [Fact] + public async Task Build_DisabledCompression_DoesNotCompressFiles() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + + // Act + var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/p:BlazorEnableCompression=false"); + + //Assert + Assert.BuildPassed(result); + + var compressedFilesPath = Path.Combine( + project.DirectoryPath, + project.IntermediateOutputDirectory, + "compressed"); + + Assert.False(Directory.Exists(compressedFilesPath)); + } + + [Fact] + public async Task Publish_WithLinkerAndCompression_UpdatesFilesWhenSourcesChange() + { + // Arrange + using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary" }); + project.TargetFramework = "netcoreapp3.1"; + var result = await MSBuildProcessManager.DotnetMSBuild(project, target: "publish"); + + Assert.BuildPassed(result); + + // Act + var mainAppDll = Path.Combine(project.DirectoryPath, project.PublishOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll"); + var mainAppDllThumbPrint = FileThumbPrint.Create(mainAppDll); + var mainAppCompressedDll = Path.Combine(project.DirectoryPath, project.PublishOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll.br"); + var mainAppCompressedDllThumbPrint = FileThumbPrint.Create(mainAppCompressedDll); + + var blazorBootJson = Path.Combine(project.DirectoryPath, project.PublishOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); + var blazorBootJsonThumbPrint = FileThumbPrint.Create(blazorBootJson); + var blazorBootJsonCompressed = Path.Combine(project.DirectoryPath, project.PublishOutputDirectory, "wwwroot", "_framework", "blazor.boot.json.br"); + var blazorBootJsonCompressedThumbPrint = FileThumbPrint.Create(blazorBootJsonCompressed); + + var programFile = Path.Combine(project.DirectoryPath, "..", "standalone", "Program.cs"); + var programFileContents = File.ReadAllText(programFile); + File.WriteAllText(programFile, programFileContents.Replace("args", "arguments")); + + // Assert + result = await MSBuildProcessManager.DotnetMSBuild(project, target: "publish"); + Assert.BuildPassed(result); + var newMainAppDllThumbPrint = FileThumbPrint.Create(mainAppDll); + var newMainAppCompressedDllThumbPrint = FileThumbPrint.Create(mainAppCompressedDll); + var newBlazorBootJsonThumbPrint = FileThumbPrint.Create(blazorBootJson); + var newBlazorBootJsonCompressedThumbPrint = FileThumbPrint.Create(blazorBootJsonCompressed); + + Assert.NotEqual(mainAppDllThumbPrint, newMainAppDllThumbPrint); + Assert.NotEqual(mainAppCompressedDllThumbPrint, newMainAppCompressedDllThumbPrint); + + Assert.NotEqual(blazorBootJsonThumbPrint, newBlazorBootJsonThumbPrint); + Assert.NotEqual(blazorBootJsonCompressedThumbPrint, newBlazorBootJsonCompressedThumbPrint); + } + + [Fact] + public async Task Publish_WithoutLinkerAndCompression_UpdatesFilesWhenSourcesChange() + { + // Arrange + using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary" }); + project.TargetFramework = "netcoreapp3.1"; + var result = await MSBuildProcessManager.DotnetMSBuild(project, target: "publish", args: "/p:BlazorWebAssemblyEnableLinking=false"); + + Assert.BuildPassed(result); + + // Act + var mainAppDll = Path.Combine(project.DirectoryPath, project.PublishOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll"); + var mainAppDllThumbPrint = FileThumbPrint.Create(mainAppDll); + + var mainAppCompressedDll = Path.Combine(project.DirectoryPath, project.PublishOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll.br"); + var mainAppCompressedDllThumbPrint = FileThumbPrint.Create(mainAppCompressedDll); + + var programFile = Path.Combine(project.DirectoryPath, "..", "standalone", "Program.cs"); + var programFileContents = File.ReadAllText(programFile); + File.WriteAllText(programFile, programFileContents.Replace("args", "arguments")); + + // Assert + result = await MSBuildProcessManager.DotnetMSBuild(project, target: "publish", args: "/p:BlazorWebAssemblyEnableLinking=false"); + Assert.BuildPassed(result); + var newMainAppDllThumbPrint = FileThumbPrint.Create(mainAppDll); + var newMainAppCompressedDllThumbPrint = FileThumbPrint.Create(mainAppCompressedDll); + + Assert.NotEqual(mainAppDllThumbPrint, newMainAppDllThumbPrint); + Assert.NotEqual(mainAppCompressedDllThumbPrint, newMainAppCompressedDllThumbPrint); + } + + [Fact] + public async Task Publish_WithLinkerAndCompression_IsIncremental() + { + // Arrange + using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary" }); + var result = await MSBuildProcessManager.DotnetMSBuild(project, target: "publish"); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + // Act + var compressedFilesFolder = Path.Combine("..", "standalone", project.IntermediateOutputDirectory, "compressed"); + var thumbPrint = FileThumbPrint.CreateFolderThumbprint(project, compressedFilesFolder); + + // Assert + for (var i = 0; i < 3; i++) + { + result = await MSBuildProcessManager.DotnetMSBuild(project); + Assert.BuildPassed(result); + + var newThumbPrint = FileThumbPrint.CreateFolderThumbprint(project, compressedFilesFolder); + Assert.Equal(thumbPrint.Count, newThumbPrint.Count); + for (var j = 0; j < thumbPrint.Count; j++) + { + Assert.Equal(thumbPrint[j], newThumbPrint[j]); + } + } + } + + [Fact] + public async Task Publish_WithoutLinkerAndCompression_IsIncremental() + { + // Arrange + using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary" }); + var result = await MSBuildProcessManager.DotnetMSBuild(project, target: "publish", args: "/p:BlazorWebAssemblyEnableLinking=false"); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + // Act + var compressedFilesFolder = Path.Combine("..", "standalone", project.IntermediateOutputDirectory, "compressed"); + var thumbPrint = FileThumbPrint.CreateFolderThumbprint(project, compressedFilesFolder); + + // Assert + for (var i = 0; i < 3; i++) + { + result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/p:BlazorWebAssemblyEnableLinking=false"); + Assert.BuildPassed(result); + + var newThumbPrint = FileThumbPrint.CreateFolderThumbprint(project, compressedFilesFolder); + Assert.Equal(thumbPrint.Count, newThumbPrint.Count); + for (var j = 0; j < thumbPrint.Count; j++) + { + Assert.Equal(thumbPrint[j], newThumbPrint[j]); + } + } + } + + [Fact] + public async Task Publish_CompressesAllFrameworkFiles() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + var result = await MSBuildProcessManager.DotnetMSBuild(project, target: "publish"); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + var extensions = new[] { ".dll", ".js", ".pdb", ".wasm", ".map", ".json", ".dat" }; + // Act + var compressedFilesPath = Path.Combine( + project.DirectoryPath, + "..", + "standalone", + project.IntermediateOutputDirectory, + "compressed", + "_framework"); + var compressedFiles = Directory.EnumerateFiles( + compressedFilesPath, + "*", + SearchOption.AllDirectories) + .Where(f => Path.GetExtension(f) == ".br") + .Select(f => Path.GetRelativePath(compressedFilesPath, f[0..^3])) + .OrderBy(f => f) + .ToArray(); + + var frameworkFilesPath = Path.Combine( + project.DirectoryPath, + project.BuildOutputDirectory, + "wwwroot", + "_framework"); + var frameworkFiles = Directory.EnumerateFiles( + frameworkFilesPath, + "*", + SearchOption.AllDirectories) + .Where(f => extensions.Contains(Path.GetExtension(f))) + .Select(f => Path.GetRelativePath(frameworkFilesPath, f)) + .OrderBy(f => f) + .ToArray(); + + Assert.Equal(frameworkFiles.Length, compressedFiles.Length); + Assert.Equal(frameworkFiles, compressedFiles); + } + } +} diff --git a/src/Components/WebAssembly/Build/test/BuildIntegrationTests/BuildIncrementalismTest.cs b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/BuildIncrementalismTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..46a85cdea19e9ca9db357832ced7319ed83c35f5 --- /dev/null +++ b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/BuildIncrementalismTest.cs @@ -0,0 +1,159 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build +{ + public class BuildIncrementalismTest + { + [Fact] + public async Task Build_WithLinker_IsIncremental() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + // Act + var thumbPrint = FileThumbPrint.CreateFolderThumbprint(project, project.BuildOutputDirectory); + + // Assert + for (var i = 0; i < 3; i++) + { + result = await MSBuildProcessManager.DotnetMSBuild(project); + Assert.BuildPassed(result); + + var newThumbPrint = FileThumbPrint.CreateFolderThumbprint(project, project.BuildOutputDirectory); + Assert.Equal(thumbPrint.Count, newThumbPrint.Count); + for (var j = 0; j < thumbPrint.Count; j++) + { + Assert.Equal(thumbPrint[j], newThumbPrint[j]); + } + } + } + + [Fact] + public async Task Build_SatelliteAssembliesFileIsPreserved() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + File.Move(Path.Combine(project.DirectoryPath, "Resources.ja.resx.txt"), Path.Combine(project.DirectoryPath, "Resource.ja.resx")); + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var satelliteAssemblyCacheFile = Path.Combine(project.IntermediateOutputDirectory, "blazor", "blazor.satelliteasm.props"); + var satelliteAssemblyFile = Path.Combine(project.BuildOutputDirectory, "wwwroot", "_framework", "_bin", "ja", "standalone.resources.dll"); + var bootJson = Path.Combine(project.DirectoryPath, project.BuildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); + + // Assert + for (var i = 0; i < 3; i++) + { + result = await MSBuildProcessManager.DotnetMSBuild(project); + Assert.BuildPassed(result); + + Verify(); + } + + // Assert - incremental builds with BuildingProject=false + for (var i = 0; i < 3; i++) + { + result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/p:BuildingProject=false"); + Assert.BuildPassed(result); + + Verify(); + } + + void Verify() + { + Assert.FileExists(result, satelliteAssemblyCacheFile); + Assert.FileExists(result, satelliteAssemblyFile); + + var bootJsonFile = JsonSerializer.Deserialize<GenerateBlazorBootJson.BootJsonData>(File.ReadAllText(bootJson), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + var satelliteResources = bootJsonFile.resources.satelliteResources; + var kvp = Assert.Single(satelliteResources); + Assert.Equal("ja", kvp.Key); + Assert.Equal("ja/standalone.resources.dll", Assert.Single(kvp.Value).Key); + } + } + + [Fact] + public async Task Build_SatelliteAssembliesFileIsCreated_IfNewFileIsAdded() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var satelliteAssemblyCacheFile = Path.Combine(project.IntermediateOutputDirectory, "blazor", "blazor.satelliteasm.props"); + var satelliteAssemblyFile = Path.Combine(project.BuildOutputDirectory, "wwwroot", "_framework", "_bin", "ja", "standalone.resources.dll"); + var bootJson = Path.Combine(project.DirectoryPath, project.BuildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); + + result = await MSBuildProcessManager.DotnetMSBuild(project); + Assert.BuildPassed(result); + + Assert.FileDoesNotExist(result, satelliteAssemblyCacheFile); + Assert.FileDoesNotExist(result, satelliteAssemblyFile); + var bootJsonFile = JsonSerializer.Deserialize<GenerateBlazorBootJson.BootJsonData>(File.ReadAllText(bootJson), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + var satelliteResources = bootJsonFile.resources.satelliteResources; + Assert.Null(satelliteResources); + + File.Move(Path.Combine(project.DirectoryPath, "Resources.ja.resx.txt"), Path.Combine(project.DirectoryPath, "Resource.ja.resx")); + result = await MSBuildProcessManager.DotnetMSBuild(project); + Assert.BuildPassed(result); + + Assert.FileExists(result, satelliteAssemblyCacheFile); + Assert.FileExists(result, satelliteAssemblyFile); + bootJsonFile = JsonSerializer.Deserialize<GenerateBlazorBootJson.BootJsonData>(File.ReadAllText(bootJson), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + satelliteResources = bootJsonFile.resources.satelliteResources; + var kvp = Assert.Single(satelliteResources); + Assert.Equal("ja", kvp.Key); + Assert.Equal("ja/standalone.resources.dll", Assert.Single(kvp.Value).Key); + } + + [Fact] + public async Task Build_SatelliteAssembliesFileIsDeleted_IfAllSatelliteFilesAreRemoved() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + File.Move(Path.Combine(project.DirectoryPath, "Resources.ja.resx.txt"), Path.Combine(project.DirectoryPath, "Resource.ja.resx")); + + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var satelliteAssemblyCacheFile = Path.Combine(project.IntermediateOutputDirectory, "blazor", "blazor.satelliteasm.props"); + var satelliteAssemblyFile = Path.Combine(project.BuildOutputDirectory, "wwwroot", "_framework", "_bin", "ja", "standalone.resources.dll"); + var bootJson = Path.Combine(project.DirectoryPath, project.BuildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); + + result = await MSBuildProcessManager.DotnetMSBuild(project); + Assert.BuildPassed(result); + + Assert.FileExists(result, satelliteAssemblyCacheFile); + Assert.FileExists(result, satelliteAssemblyFile); + var bootJsonFile = JsonSerializer.Deserialize<GenerateBlazorBootJson.BootJsonData>(File.ReadAllText(bootJson), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + var satelliteResources = bootJsonFile.resources.satelliteResources; + var kvp = Assert.Single(satelliteResources); + Assert.Equal("ja", kvp.Key); + Assert.Equal("ja/standalone.resources.dll", Assert.Single(kvp.Value).Key); + + + File.Delete(Path.Combine(project.DirectoryPath, "Resource.ja.resx")); + result = await MSBuildProcessManager.DotnetMSBuild(project); + Assert.BuildPassed(result); + + Assert.FileDoesNotExist(result, satelliteAssemblyCacheFile); + bootJsonFile = JsonSerializer.Deserialize<GenerateBlazorBootJson.BootJsonData>(File.ReadAllText(bootJson), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + satelliteResources = bootJsonFile.resources.satelliteResources; + Assert.Null(satelliteResources); + } + } +} diff --git a/src/Components/WebAssembly/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..f64abc9b6ea74b3f096615cd1d40549e59a52238 --- /dev/null +++ b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/BuildIntegrationTest.cs @@ -0,0 +1,359 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; +using Xunit; +using static Microsoft.AspNetCore.Components.WebAssembly.Build.WebAssemblyRuntimePackage; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build +{ + public class BuildIntegrationTest + { + [Fact] + public async Task Build_WithDefaultSettings_Works() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + project.Configuration = "Debug"; + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", DotNetJsFileName); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.timezones.dat"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "RazorClassLibrary.dll"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.pdb"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "RazorClassLibrary.pdb"); + + var staticWebAssets = Assert.FileExists(result, buildOutputDirectory, "standalone.StaticWebAssets.xml"); + Assert.FileContains(result, staticWebAssets, Path.Combine("netstandard2.1", "wwwroot")); + } + + [Fact] + public async Task Build_InRelease_Works() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + project.Configuration = "Release"; + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", DotNetJsFileName); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.timezones.dat"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "RazorClassLibrary.dll"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + Assert.FileDoesNotExist(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.pdb"); + Assert.FileDoesNotExist(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "RazorClassLibrary.pdb"); + + var staticWebAssets = Assert.FileExists(result, buildOutputDirectory, "standalone.StaticWebAssets.xml"); + Assert.FileContains(result, staticWebAssets, Path.Combine("netstandard2.1", "wwwroot")); + } + + [Fact] + public async Task Build_ProducesBootJsonDataWithExpectedContent() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + project.Configuration = "Debug"; + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + var bootJsonPath = Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); + var bootJsonData = ReadBootJsonData(result, bootJsonPath); + + var runtime = bootJsonData.resources.runtime.Keys; + Assert.Contains(DotNetJsFileName, runtime); + Assert.Contains("dotnet.wasm", runtime); + Assert.Contains("dotnet.timezones.dat", runtime); + + var assemblies = bootJsonData.resources.assembly.Keys; + Assert.Contains("standalone.dll", assemblies); + Assert.Contains("RazorClassLibrary.dll", assemblies); + Assert.Contains("Microsoft.Extensions.Logging.Abstractions.dll", assemblies); + + var pdb = bootJsonData.resources.pdb.Keys; + Assert.Contains("standalone.pdb", pdb); + Assert.Contains("RazorClassLibrary.pdb", pdb); + + Assert.Null(bootJsonData.resources.satelliteResources); + } + + [Fact] + public async Task Build_InRelease_ProducesBootJsonDataWithExpectedContent() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + project.Configuration = "Release"; + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + var bootJsonPath = Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); + var bootJsonData = ReadBootJsonData(result, bootJsonPath); + + var runtime = bootJsonData.resources.runtime.Keys; + Assert.Contains(DotNetJsFileName, runtime); + Assert.Contains("dotnet.wasm", runtime); + Assert.Contains("dotnet.timezones.dat", runtime); + + var assemblies = bootJsonData.resources.assembly.Keys; + Assert.Contains("standalone.dll", assemblies); + Assert.Contains("RazorClassLibrary.dll", assemblies); + Assert.Contains("Microsoft.Extensions.Logging.Abstractions.dll", assemblies); + + Assert.Null(bootJsonData.resources.pdb); + Assert.Null(bootJsonData.resources.satelliteResources); + } + + [Fact] + public async Task Build_WithBlazorEnableTimeZoneSupportDisabled_DoesNotCopyTimeZoneInfo() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + project.Configuration = "Release"; + project.AddProjectFileContent( +@" +<PropertyGroup> + <BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport> +</PropertyGroup>"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + var bootJsonPath = Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); + var bootJsonData = ReadBootJsonData(result, bootJsonPath); + + var runtime = bootJsonData.resources.runtime.Keys; + Assert.Contains("dotnet.wasm", runtime); + Assert.DoesNotContain("dotnet.timezones.dat", runtime); + + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.wasm"); + Assert.FileDoesNotExist(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.timezones.dat"); + } + + [Fact] + public async Task Build_Hosted_Works() + { + // Arrange + using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", }); + project.TargetFramework = "netcoreapp3.1"; + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + var path = Path.GetFullPath(Path.Combine(project.SolutionPath, "standalone", "bin", project.Configuration, "netstandard2.1", "standalone.dll")); + Assert.FileDoesNotExist(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll"); + + var staticWebAssets = Assert.FileExists(result, buildOutputDirectory, "blazorhosted.StaticWebAssets.xml"); + Assert.FileContains(result, staticWebAssets, Path.Combine("netstandard2.1", "wwwroot")); + Assert.FileContains(result, staticWebAssets, Path.Combine("razorclasslibrary", "wwwroot")); + Assert.FileContains(result, staticWebAssets, Path.Combine("standalone", "wwwroot")); + } + + [Fact] + public async Task Build_WithLinkOnBuildDisabled_Works() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + project.AddProjectFileContent( +@"<PropertyGroup> + <BlazorWebAssemblyEnableLinking>false</BlazorWebAssemblyEnableLinking> +</PropertyGroup>"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", DotNetJsFileName); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.timezones.dat"); + + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + } + + [Fact] + public async Task Build_SatelliteAssembliesAreCopiedToBuildOutput() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" }); + project.AddProjectFileContent( +@" +<PropertyGroup> + <DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants> +</PropertyGroup> +<ItemGroup> + <ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" /> +</ItemGroup>"); + var resxfileInProject = Path.Combine(project.DirectoryPath, "Resources.ja.resx.txt"); + File.Move(resxfileInProject, Path.Combine(project.DirectoryPath, "Resource.ja.resx")); + + var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore"); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output. + + var bootJsonPath = Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); + Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\""); + Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); + } + + [Fact] + public async Task Build_WithBlazorWebAssemblyEnableLinkingFalse_SatelliteAssembliesAreCopiedToBuildOutput() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" }); + project.AddProjectFileContent( +@" +<PropertyGroup> + <BlazorWebAssemblyEnableLinking>false</BlazorWebAssemblyEnableLinking> + <DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants> +</PropertyGroup> +<ItemGroup> + <ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" /> +</ItemGroup>"); + + var resxfileInProject = Path.Combine(project.DirectoryPath, "Resources.ja.resx.txt"); + File.Move(resxfileInProject, Path.Combine(project.DirectoryPath, "Resource.ja.resx")); + + var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore"); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "classlibrarywithsatelliteassemblies.dll"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output. + + var bootJson = ReadBootJsonData(result, Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json")); + + var satelliteResources = bootJson.resources.satelliteResources; + Assert.NotNull(satelliteResources); + + Assert.Contains("es-ES", satelliteResources.Keys); + Assert.Contains("es-ES/classlibrarywithsatelliteassemblies.resources.dll", satelliteResources["es-ES"].Keys); + Assert.Contains("fr", satelliteResources.Keys); + Assert.Contains("fr/Microsoft.CodeAnalysis.CSharp.resources.dll", satelliteResources["fr"].Keys); + Assert.Contains("ja", satelliteResources.Keys); + Assert.Contains("ja/standalone.resources.dll", satelliteResources["ja"].Keys); + } + + + [Fact] + public async Task Build_WithI8NOption_CopiesI8NAssembliesWithoutLinkerEnabled() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + project.Configuration = "Debug"; + project.AddProjectFileContent( +@" +<PropertyGroup> + <BlazorWebAssemblyI18NAssemblies>other</BlazorWebAssemblyI18NAssemblies> +</PropertyGroup>"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "I18N.dll"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "I18N.Other.dll"); + // When running without linker, we copy all I18N assemblies. Look for one additional + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "I18N.West.dll"); + } + + [Fact] + public async Task Build_WithI8NOption_CopiesI8NAssembliesWithLinkerEnabled() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + project.Configuration = "Debug"; + project.AddProjectFileContent( +@" +<PropertyGroup> + <BlazorWebAssemblyEnableLinking>true</BlazorWebAssemblyEnableLinking> + <BlazorWebAssemblyI18NAssemblies>other</BlazorWebAssemblyI18NAssemblies> +</PropertyGroup>"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "I18N.dll"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "I18N.Other.dll"); + Assert.FileDoesNotExist(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "I18N.West.dll"); + } + + [Fact] + public async Task Build_WithCustomOutputPath_Works() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + + project.AddDirectoryBuildContent( +@"<PropertyGroup> + <BaseOutputPath>$(MSBuildThisFileDirectory)build\bin\</BaseOutputPath> + <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)build\obj\</BaseIntermediateOutputPath> +</PropertyGroup>"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/restore"); + Assert.BuildPassed(result); + + var compressedFilesPath = Path.Combine( + project.DirectoryPath, + "build", + project.IntermediateOutputDirectory, + "compressed"); + + Assert.True(Directory.Exists(compressedFilesPath)); + } + + private static GenerateBlazorBootJson.BootJsonData ReadBootJsonData(MSBuildResult result, string path) + { + return JsonSerializer.Deserialize<GenerateBlazorBootJson.BootJsonData>( + File.ReadAllText(Path.Combine(result.Project.DirectoryPath, path)), + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + } + } +} diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/FileThumbPrint.cs b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/FileThumbPrint.cs similarity index 97% rename from src/Components/Blazor/Build/test/BuildIntegrationTests/FileThumbPrint.cs rename to src/Components/WebAssembly/Build/test/BuildIntegrationTests/FileThumbPrint.cs index 58b5499e8bc64eb6044f2a04c021dd8243e21d19..c5f210be52758ac24af2a8a70f090b1c7d29c1e3 100644 --- a/src/Components/Blazor/Build/test/BuildIntegrationTests/FileThumbPrint.cs +++ b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/FileThumbPrint.cs @@ -7,7 +7,7 @@ using System.IO; using System.Linq; using System.Security.Cryptography; -namespace Microsoft.AspNetCore.Blazor.Build +namespace Microsoft.AspNetCore.Components.WebAssembly.Build { internal class FileThumbPrint : IEquatable<FileThumbPrint> { diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/MSBuildProcessManager.cs b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/MSBuildProcessManager.cs similarity index 98% rename from src/Components/Blazor/Build/test/BuildIntegrationTests/MSBuildProcessManager.cs rename to src/Components/WebAssembly/Build/test/BuildIntegrationTests/MSBuildProcessManager.cs index b7e16ca072917fb1f4a88402c59e537f776dae96..c9e51763994f589189897cc3b72920d3d7ebaef3 100644 --- a/src/Components/Blazor/Build/test/BuildIntegrationTests/MSBuildProcessManager.cs +++ b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/MSBuildProcessManager.cs @@ -12,7 +12,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.CommandLineUtils; -namespace Microsoft.AspNetCore.Blazor.Build +namespace Microsoft.AspNetCore.Components.WebAssembly.Build { internal static class MSBuildProcessManager { @@ -33,6 +33,7 @@ namespace Microsoft.AspNetCore.Blazor.Build buildArgumentList.Add($"/t:{target}"); buildArgumentList.Add($"/p:Configuration={project.Configuration}"); + buildArgumentList.Add($"/p:BlazorBuildConfiguration={ProjectDirectory.TestProjectConfiguration}"); buildArgumentList.Add(args); var buildArguments = string.Join(" ", buildArgumentList); diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/MSBuildResult.cs b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/MSBuildResult.cs similarity index 92% rename from src/Components/Blazor/Build/test/BuildIntegrationTests/MSBuildResult.cs rename to src/Components/WebAssembly/Build/test/BuildIntegrationTests/MSBuildResult.cs index 9a83df922b01cc8a36b7c65c58726ba926cf38a7..4ad24e269d0a72600aa6cbd8398503d6a4a9e4f4 100644 --- a/src/Components/Blazor/Build/test/BuildIntegrationTests/MSBuildResult.cs +++ b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/MSBuildResult.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace Microsoft.AspNetCore.Blazor.Build +namespace Microsoft.AspNetCore.Components.WebAssembly.Build { internal class MSBuildResult { diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/ProjectDirectory.cs b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/ProjectDirectory.cs similarity index 89% rename from src/Components/Blazor/Build/test/BuildIntegrationTests/ProjectDirectory.cs rename to src/Components/WebAssembly/Build/test/BuildIntegrationTests/ProjectDirectory.cs index e50b750ae491917fff833b4fbc7099b63c598515..668ba8b4fceb61eef7f6f14c3ef15e85002da50e 100644 --- a/src/Components/Blazor/Build/test/BuildIntegrationTests/ProjectDirectory.cs +++ b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/ProjectDirectory.cs @@ -8,12 +8,22 @@ using System.Linq; using System.Reflection; using System.Threading; -namespace Microsoft.AspNetCore.Blazor.Build +namespace Microsoft.AspNetCore.Components.WebAssembly.Build { internal class ProjectDirectory : IDisposable { public bool PreserveWorkingDirectory { get; set; } = false; + // Configuration the test project is building in. + public static readonly string TestProjectConfiguration +#if DEBUG + = "Debug"; +#elif RELEASE + = "Release"; +#else +#error Configuration not supported +#endif + private static readonly string RepoRoot = GetTestAttribute("Testing.RepoRoot"); public static ProjectDirectory Create(string projectName, string baseDirectory = "", string[] additionalProjects = null) @@ -33,7 +43,7 @@ namespace Microsoft.AspNetCore.Blazor.Build throw new InvalidOperationException("RepoRoot was not specified."); } - var testAppsRoot = Path.Combine(RepoRoot, "src", "Components", "Blazor", "Build", "testassets"); + var testAppsRoot = Path.Combine(RepoRoot, "src", "Components", "WebAssembly", "Build", "testassets"); foreach (var project in new string[] { projectName, }.Concat(additionalProjects ?? Array.Empty<string>())) { var projectRoot = Path.Combine(testAppsRoot, project); @@ -142,13 +152,7 @@ $@"<Project> public string TargetFramework { get; set; } = "netstandard2.1"; -#if DEBUG - public string Configuration => "Debug"; -#elif RELEASE - public string Configuration => "Release"; -#else -#error Configuration not supported -#endif + public string Configuration { get; set; } = TestProjectConfiguration; public string IntermediateOutputDirectory => Path.Combine("obj", Configuration, TargetFramework); @@ -168,6 +172,20 @@ $@"<Project> File.WriteAllText(ProjectFilePath, updated); } + internal void AddDirectoryBuildContent(string content) + { + if (content == null) + { + throw new ArgumentNullException(nameof(content)); + } + + var filepath = Path.Combine(DirectoryPath, "Directory.Build.props"); + + var existing = File.ReadAllText(filepath); + var updated = existing.Replace("<!-- Test Placeholder -->", content); + File.WriteAllText(filepath, updated); + } + public void Dispose() { if (PreserveWorkingDirectory) @@ -207,5 +225,7 @@ $@"<Project> .FirstOrDefault(f => f.Key == key) ?.Value; } + + public override string ToString() => DirectoryPath; } } diff --git a/src/Components/Blazor/Build/test/BuildIntegrationTests/ProjectDirectoryTest.cs b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/ProjectDirectoryTest.cs similarity index 83% rename from src/Components/Blazor/Build/test/BuildIntegrationTests/ProjectDirectoryTest.cs rename to src/Components/WebAssembly/Build/test/BuildIntegrationTests/ProjectDirectoryTest.cs index 23badb4296f86425cff5559f83be78fcbc1e08b3..130a14ef8dd0a851862004ea37540afadb86813c 100644 --- a/src/Components/Blazor/Build/test/BuildIntegrationTests/ProjectDirectoryTest.cs +++ b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/ProjectDirectoryTest.cs @@ -3,7 +3,7 @@ using Xunit; -namespace Microsoft.AspNetCore.Blazor.Build +namespace Microsoft.AspNetCore.Components.WebAssembly.Build { public class ProjectDirectoryTest { @@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Blazor.Build public void ProjectDirectory_IsNotSetToBePreserved() { // Arrange - using var project = ProjectDirectory.Create("standalone"); + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); // Act & Assert // This flag is only meant for local debugging and should not be set when checking in. diff --git a/src/Components/WebAssembly/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..21aa66f6feccdf5958d24bcfbca65dfcd427bd0e --- /dev/null +++ b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/PublishIntegrationTest.cs @@ -0,0 +1,656 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Blazor.Build; +using Xunit; +using ResourceHashesByNameDictionary = System.Collections.Generic.Dictionary<string, string>; +using static Microsoft.AspNetCore.Components.WebAssembly.Build.WebAssemblyRuntimePackage; +using System.IO.Compression; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build +{ + public class PublishIntegrationTest + { + [Fact] + public async Task Publish_WithDefaultSettings_Works() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new [] { "razorclasslibrary", "LinkBaseToWebRoot" }); + project.Configuration = "Debug"; + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish"); + + Assert.BuildPassed(result); + + var publishDirectory = project.PublishOutputDirectory; + + var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot"); + + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", DotNetJsFileName); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + + // Verify referenced static web assets + Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); + Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "styles.css"); + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "index.html"); + + // Verify link item assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "js", "LinkedScript.js"); + var cssFile = Assert.FileExists(result, blazorPublishDirectory, "css", "app.css"); + Assert.FileContains(result, cssFile, ".publish"); + Assert.FileDoesNotExist(result, "dist", "Fake-License.txt"); + + // Verify web.config + Assert.FileExists(result, publishDirectory, "web.config"); + Assert.FileCountEquals(result, 1, publishDirectory, "*", SearchOption.TopDirectoryOnly); + + VerifyBootManifestHashes(result, blazorPublishDirectory); + VerifyServiceWorkerFiles(result, blazorPublishDirectory, + serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), + serviceWorkerContent: "// This is the production service worker", + assetsManifestPath: "custom-service-worker-assets.js"); + + // When publishing without linker, we expect to have collation information present in mscorlib.dll + Assert.AssemblyContainsResource(result, Path.Combine(blazorPublishDirectory, "_framework", "_bin", "mscorlib.dll"), "collation.cjkCHS.bin"); + } + + [Fact] + public async Task Publish_InRelease_Works() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new [] { "razorclasslibrary", "LinkBaseToWebRoot" }); + project.Configuration = "Release"; + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish"); + + Assert.BuildPassed(result); + + var publishDirectory = project.PublishOutputDirectory; + + var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot"); + + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", DotNetJsFileName); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + + // Verify referenced static web assets + Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); + Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "styles.css"); + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "index.html"); + + // Verify link item assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "js", "LinkedScript.js"); + var cssFile = Assert.FileExists(result, blazorPublishDirectory, "css", "app.css"); + Assert.FileContains(result, cssFile, ".publish"); + Assert.FileDoesNotExist(result, "dist", "Fake-License.txt"); + + // Verify web.config + Assert.FileExists(result, publishDirectory, "web.config"); + Assert.FileCountEquals(result, 1, publishDirectory, "*", SearchOption.TopDirectoryOnly); + + // When publishing with linker, we expect to have collation information present in mscorlib.dll + Assert.AssemblyContainsResource(result, Path.Combine(blazorPublishDirectory, "_framework", "_bin", "mscorlib.dll"), "collation.cjkCHS.bin"); + } + + [Fact] + public async Task Publish_LinkerEnabledAndBlazorWebAssemblyPreserveCollationDataSet_CollationInformationIsPreserved() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + project.Configuration = "Release"; + project.AddProjectFileContent( +@"<PropertyGroup> + <BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData> +</PropertyGroup>"); + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish"); + + Assert.BuildPassed(result); + + var publishDirectory = project.PublishOutputDirectory; + + var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot"); + + // When publishing with BlazorWebAssemblyPreserveCollationData=false, collation information should be stripped out. + Assert.AssemblyDoesNotContainResource(result, Path.Combine(blazorPublishDirectory, "_framework", "_bin", "mscorlib.dll"), "collation.cjkCHS.bin"); + } + + [Fact] + public async Task Publish_WithExistingWebConfig_Works() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "LinkBaseToWebRoot" }); + project.Configuration = "Release"; + + var webConfigContents = "test webconfig contents"; + AddFileToProject(project, "web.config", webConfigContents); + + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish"); + + Assert.BuildPassed(result); + + var publishDirectory = project.PublishOutputDirectory; + + // Verify web.config + Assert.FileExists(result, publishDirectory, "web.config"); + Assert.FileContains(result, Path.Combine(publishDirectory, "web.config"), webConfigContents); + } + + [Fact] + public async Task Publish_WithNoBuild_Works() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build"); + + Assert.BuildPassed(result); + + result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:NoBuild=true"); + + Assert.BuildPassed(result); + + var publishDirectory = project.PublishOutputDirectory; + var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot"); + + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", DotNetJsFileName); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "index.html"); + + // Verify static web assets from referenced projects are copied. + Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); + Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "styles.css"); + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "index.html"); + + // Verify web.config + Assert.FileExists(result, publishDirectory, "web.config"); + + VerifyBootManifestHashes(result, blazorPublishDirectory); + VerifyServiceWorkerFiles(result, blazorPublishDirectory, + serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), + serviceWorkerContent: "// This is the production service worker", + assetsManifestPath: "custom-service-worker-assets.js"); + + VerifyCompression(result, blazorPublishDirectory); + } + + private static void VerifyCompression(MSBuildResult result, string blazorPublishDirectory) + { + var original = Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json"); + var compressed = Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json.br"); + using var brotliStream = new BrotliStream(File.OpenRead(compressed), CompressionMode.Decompress); + using var textReader = new StreamReader(brotliStream); + var uncompressedText = textReader.ReadToEnd(); + var originalText = File.ReadAllText(original); + + Assert.Equal(originalText, uncompressedText); + } + + [Fact] + public async Task Publish_WithLinkOnBuildDisabled_Works() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new [] { "razorclasslibrary" }); + project.AddProjectFileContent( +@"<PropertyGroup> + <BlazorWebAssemblyEnableLinking>false</BlazorWebAssemblyEnableLinking> +</PropertyGroup>"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish"); + + Assert.BuildPassed(result); + + var publishDirectory = project.PublishOutputDirectory; + var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot"); + + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", DotNetJsFileName); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "index.html"); + + // Verify referenced static web assets + Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); + Assert.FileExists(result, blazorPublishDirectory, "_content", "RazorClassLibrary", "styles.css"); + + // Verify web.config + Assert.FileExists(result, publishDirectory, "web.config"); + + VerifyBootManifestHashes(result, blazorPublishDirectory); + VerifyServiceWorkerFiles(result, blazorPublishDirectory, + serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), + serviceWorkerContent: "// This is the production service worker", + assetsManifestPath: "custom-service-worker-assets.js"); + } + + [Fact] + public async Task Publish_SatelliteAssemblies_AreCopiedToBuildOutput() + { + // Arrange + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary", "classlibrarywithsatelliteassemblies" }); + project.AddProjectFileContent( +@" +<PropertyGroup> + <DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants> +</PropertyGroup> +<ItemGroup> + <ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" /> +</ItemGroup>"); + + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", args: "/restore"); + + Assert.BuildPassed(result); + + var publishDirectory = project.PublishOutputDirectory; + var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot"); + + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.CodeAnalysis.CSharp.dll"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.CSharp.resources.dll"); // Verify satellite assemblies are present in the build output. + + var bootJsonPath = Path.Combine(blazorPublishDirectory, "_framework", "blazor.boot.json"); + Assert.FileContains(result, bootJsonPath, "\"Microsoft.CodeAnalysis.CSharp.dll\""); + Assert.FileContains(result, bootJsonPath, "\"fr\\/Microsoft.CodeAnalysis.CSharp.resources.dll\""); + + VerifyBootManifestHashes(result, blazorPublishDirectory); + VerifyServiceWorkerFiles(result, blazorPublishDirectory, + serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), + serviceWorkerContent: "// This is the production service worker", + assetsManifestPath: "custom-service-worker-assets.js"); + } + + [Fact] + public async Task Publish_HostedApp_WithLinkOnBuildTrue_Works() + { + // Arrange + using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", }); + project.TargetFramework = "netcoreapp3.1"; + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish"); + AddSiblingProjectFileContent(project, @" +<PropertyGroup> + <BlazorWebAssemblyEnableLinking>true</BlazorWebAssemblyEnableLinking> +</PropertyGroup>"); + + Assert.BuildPassed(result); + + var publishDirectory = project.PublishOutputDirectory; + // Make sure the main project exists + Assert.FileExists(result, publishDirectory, "blazorhosted.dll"); + Assert.FileExists(result, publishDirectory, "RazorClassLibrary.dll"); + + var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", DotNetJsFileName); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + // Verify project references appear as static web assets + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "RazorClassLibrary.dll"); + // Also verify project references to the server project appear in the publish output + Assert.FileExists(result, publishDirectory, "RazorClassLibrary.dll"); + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "index.html"); + + // Verify static web assets from referenced projects are copied. + Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); + Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css"); + + // Verify web.config + Assert.FileExists(result, publishDirectory, "web.config"); + + VerifyBootManifestHashes(result, blazorPublishDirectory); + VerifyServiceWorkerFiles(result, blazorPublishDirectory, + serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), + serviceWorkerContent: "// This is the production service worker", + assetsManifestPath: "custom-service-worker-assets.js"); + } + + [Fact] + // Regression test for https://github.com/dotnet/aspnetcore/issues/18752 + public async Task Publish_HostedApp_WithLinkOnBuildFalse_Works() + { + // Arrange + using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", }); + project.TargetFramework = "netcoreapp3.1"; + + AddSiblingProjectFileContent(project, @" +<PropertyGroup> + <BlazorWebAssemblyEnableLinking>false</BlazorWebAssemblyEnableLinking> +</PropertyGroup>"); + + // VS builds projects individually and then a publish with BuildDependencies=false, but building the main project is a close enough approximation for this test. + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build"); + + Assert.BuildPassed(result); + + // Publish + result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:BuildDependencies=false"); + + var publishDirectory = project.PublishOutputDirectory; + // Make sure the main project exists + Assert.FileExists(result, publishDirectory, "blazorhosted.dll"); + + // Verification for https://github.com/dotnet/aspnetcore/issues/19926. Verify binaries for projects + // referenced by the Hosted project appear in the publish directory + Assert.FileExists(result, publishDirectory, "RazorClassLibrary.dll"); + Assert.FileExists(result, publishDirectory, "standalone.dll"); + + var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", DotNetJsFileName); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "RazorClassLibrary.dll"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "index.html"); + + // Verify static web assets from referenced projects are copied. + Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); + Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css"); + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "index.html"); + + // Verify web.config + Assert.FileExists(result, publishDirectory, "web.config"); + + VerifyBootManifestHashes(result, blazorPublishDirectory); + VerifyServiceWorkerFiles(result, blazorPublishDirectory, + serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), + serviceWorkerContent: "// This is the production service worker", + assetsManifestPath: "custom-service-worker-assets.js"); + + } + + [Fact] + public async Task Publish_HostedApp_WithNoBuild_Works() + { + // Arrange + using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", }); + project.TargetFramework = "netcoreapp3.1"; + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build"); + + Assert.BuildPassed(result); + + result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:NoBuild=true"); + + var publishDirectory = project.PublishOutputDirectory; + // Make sure the main project exists + Assert.FileExists(result, publishDirectory, "blazorhosted.dll"); + + var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", DotNetJsFileName); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "index.html"); + + // Verify static web assets from referenced projects are copied. + Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); + Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css"); + + // Verify web.config + Assert.FileExists(result, publishDirectory, "web.config"); + + VerifyBootManifestHashes(result, blazorPublishDirectory); + VerifyServiceWorkerFiles(result, blazorPublishDirectory, + serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), + serviceWorkerContent: "// This is the production service worker", + assetsManifestPath: "custom-service-worker-assets.js"); + } + + [Fact] + public async Task Publish_HostedApp_VisualStudio() + { + // Simulates publishing the same way VS does by setting BuildProjectReferences=false. + // Arrange + using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", }); + project.TargetFramework = "netcoreapp3.1"; + project.Configuration = "Release"; + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build", "/p:BuildInsideVisualStudio=true"); + + Assert.BuildPassed(result); + + result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:BuildProjectReferences=false /p:BuildInsideVisualStudio=true"); + + var publishDirectory = project.PublishOutputDirectory; + // Make sure the main project exists + Assert.FileExists(result, publishDirectory, "blazorhosted.dll"); + + var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", "dotnet.wasm"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "wasm", DotNetJsFileName); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + + // Verify static assets are in the publish directory + Assert.FileExists(result, blazorPublishDirectory, "index.html"); + + // Verify static web assets from referenced projects are copied. + Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "wwwroot", "exampleJsInterop.js"); + Assert.FileExists(result, publishDirectory, "wwwroot", "_content", "RazorClassLibrary", "styles.css"); + + // Verify web.config + Assert.FileExists(result, publishDirectory, "web.config"); + + VerifyBootManifestHashes(result, blazorPublishDirectory); + VerifyServiceWorkerFiles(result, blazorPublishDirectory, + serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), + serviceWorkerContent: "// This is the production service worker", + assetsManifestPath: "custom-service-worker-assets.js"); + } + + // Regression test to verify satellite assemblies from the blazor app are copied to the published app's wwwroot output directory as + // part of publishing in VS + [Fact] + public async Task Publish_HostedApp_VisualStudio_WithSatelliteAssemblies() + { + // Simulates publishing the same way VS does by setting BuildProjectReferences=false. + // Arrange + var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "standalone", "razorclasslibrary", "classlibrarywithsatelliteassemblies" }); + project.TargetFramework = "netcoreapp3.1"; + project.Configuration = "Release"; + var standaloneProjectDirectory = Path.Combine(project.DirectoryPath, "..", "standalone"); + + // Setup the standalone app to have it's own satellite assemblies as well as transitively imported satellite assemblies. + var resxfileInProject = Path.Combine(standaloneProjectDirectory , "Resources.ja.resx.txt"); + File.Move(resxfileInProject, Path.Combine(standaloneProjectDirectory, "Resource.ja.resx")); + + var standaloneProjFile = Path.Combine(standaloneProjectDirectory, "standalone.csproj"); + var existing = File.ReadAllText(standaloneProjFile); + var updatedContent = @" +<PropertyGroup> + <DefineConstants>$(DefineConstants);REFERENCE_classlibrarywithsatelliteassemblies</DefineConstants> +</PropertyGroup> +<ItemGroup> + <ProjectReference Include=""..\classlibrarywithsatelliteassemblies\classlibrarywithsatelliteassemblies.csproj"" /> +</ItemGroup>"; + var updated = existing.Replace("<!-- Test Placeholder -->", updatedContent); + File.WriteAllText(standaloneProjFile, updated); + + var result = await MSBuildProcessManager.DotnetMSBuild(project, "Build", $"/restore"); + + Assert.BuildPassed(result); + + result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish", "/p:BuildProjectReferences=false"); + + var publishDirectory = project.PublishOutputDirectory; + // Make sure the main project exists + Assert.FileExists(result, publishDirectory, "blazorhosted.dll"); + + var blazorPublishDirectory = Path.Combine(publishDirectory, "wwwroot"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "ja", "standalone.resources.dll"); + Assert.FileExists(result, blazorPublishDirectory, "_framework", "_bin", "fr", "Microsoft.CodeAnalysis.resources.dll"); + + var bootJson = Path.Combine(project.DirectoryPath, blazorPublishDirectory, "_framework", "blazor.boot.json"); + var bootJsonFile = JsonSerializer.Deserialize<GenerateBlazorBootJson.BootJsonData>(File.ReadAllText(bootJson), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + var satelliteResources = bootJsonFile.resources.satelliteResources; + + Assert.Contains("es-ES", satelliteResources.Keys); + Assert.Contains("es-ES/classlibrarywithsatelliteassemblies.resources.dll", satelliteResources["es-ES"].Keys); + Assert.Contains("fr", satelliteResources.Keys); + Assert.Contains("fr/Microsoft.CodeAnalysis.CSharp.resources.dll", satelliteResources["fr"].Keys); + Assert.Contains("ja", satelliteResources.Keys); + Assert.Contains("ja/standalone.resources.dll", satelliteResources["ja"].Keys); + + VerifyServiceWorkerFiles(result, blazorPublishDirectory, + serviceWorkerPath: Path.Combine("serviceworkers", "my-service-worker.js"), + serviceWorkerContent: "// This is the production service worker", + assetsManifestPath: "custom-service-worker-assets.js"); + } + + private static void AddSiblingProjectFileContent(ProjectDirectory project, string content) + { + var path = Path.Combine(project.SolutionPath, "standalone", "standalone.csproj"); + var existing = File.ReadAllText(path); + var updated = existing.Replace("<!-- Test Placeholder -->", content); + File.WriteAllText(path, updated); + } + + private static void AddFileToProject(ProjectDirectory project, string filename, string content) + { + var path = Path.Combine(project.DirectoryPath, filename); + File.WriteAllText(path, content); + } + + private static void VerifyBootManifestHashes(MSBuildResult result, string blazorPublishDirectory) + { + var bootManifestResolvedPath = Assert.FileExists(result, blazorPublishDirectory, "_framework", "blazor.boot.json"); + var bootManifestJson = File.ReadAllText(bootManifestResolvedPath); + var bootManifest = JsonSerializer.Deserialize<GenerateBlazorBootJson.BootJsonData>(bootManifestJson); + + VerifyBootManifestHashes(result, blazorPublishDirectory, bootManifest.resources.assembly, r => $"_framework/_bin/{r}"); + VerifyBootManifestHashes(result, blazorPublishDirectory, bootManifest.resources.runtime, r => $"_framework/wasm/{r}"); + + if (bootManifest.resources.pdb != null) + { + VerifyBootManifestHashes(result, blazorPublishDirectory, bootManifest.resources.pdb, r => $"_framework/_bin/{r}"); + } + + if (bootManifest.resources.satelliteResources != null) + { + foreach (var resourcesForCulture in bootManifest.resources.satelliteResources.Values) + { + VerifyBootManifestHashes(result, blazorPublishDirectory, resourcesForCulture, r => $"_framework/_bin/{r}"); + } + } + } + + private static void VerifyBootManifestHashes(MSBuildResult result, string blazorPublishDirectory, ResourceHashesByNameDictionary resources, Func<string, string> relativePathFunc) + { + foreach (var (name, hash) in resources) + { + var relativePath = Path.Combine(blazorPublishDirectory, relativePathFunc(name)); + Assert.FileHashEquals(result, relativePath, ParseWebFormattedHash(hash)); + } + } + + private static void VerifyServiceWorkerFiles(MSBuildResult result, string blazorPublishDirectory, string serviceWorkerPath, string serviceWorkerContent, string assetsManifestPath) + { + // Check the expected files are there + var serviceWorkerResolvedPath = Assert.FileExists(result, blazorPublishDirectory, serviceWorkerPath); + var assetsManifestResolvedPath = Assert.FileExists(result, blazorPublishDirectory, assetsManifestPath); + + // Check the service worker contains the expected content (which comes from the PublishedContent file) + Assert.FileContains(result, serviceWorkerResolvedPath, serviceWorkerContent); + + // Check the assets manifest version was added to the published service worker + var assetsManifest = ReadServiceWorkerAssetsManifest(assetsManifestResolvedPath); + Assert.FileContains(result, serviceWorkerResolvedPath, $"/* Manifest version: {assetsManifest.version} */"); + + // Check the assets manifest contains correct entries for all static content we're publishing + var resolvedPublishDirectory = Path.Combine(result.Project.DirectoryPath, blazorPublishDirectory); + var publishedStaticFiles = Directory.GetFiles(resolvedPublishDirectory, "*", new EnumerationOptions { RecurseSubdirectories = true }); + var assetsManifestHashesByUrl = (IReadOnlyDictionary<string, string>)assetsManifest.assets.ToDictionary(x => x.url, x => x.hash); + foreach (var publishedFilePath in publishedStaticFiles) + { + var publishedFileRelativePath = Path.GetRelativePath(resolvedPublishDirectory, publishedFilePath); + + // We don't list compressed files in the SWAM, as these are transparent to the client, + // nor do we list the service worker itself or its assets manifest, as these don't need to be fetched in the same way + if (IsCompressedFile(publishedFileRelativePath) + || string.Equals(publishedFileRelativePath, serviceWorkerPath, StringComparison.Ordinal) + || string.Equals(publishedFileRelativePath, assetsManifestPath, StringComparison.Ordinal)) + { + continue; + } + + // Verify hash + var publishedFileUrl = publishedFileRelativePath.Replace('\\', '/'); + var expectedHash = ParseWebFormattedHash(assetsManifestHashesByUrl[publishedFileUrl]); + Assert.Contains(publishedFileUrl, assetsManifestHashesByUrl); + Assert.FileHashEquals(result, publishedFilePath, expectedHash); + } + } + + private static string ParseWebFormattedHash(string webFormattedHash) + { + Assert.StartsWith("sha256-", webFormattedHash); + return webFormattedHash.Substring(7); + } + + private static bool IsCompressedFile(string path) + { + switch (Path.GetExtension(path)) + { + case ".br": + case ".gz": + return true; + default: + return false; + } + } + + private static GenerateServiceWorkerAssetsManifest.AssetsManifestFile ReadServiceWorkerAssetsManifest(string assetsManifestResolvedPath) + { + var jsContents = File.ReadAllText(assetsManifestResolvedPath); + var jsonStart = jsContents.IndexOf("{"); + var jsonLength = jsContents.LastIndexOf("}") - jsonStart + 1; + var json = jsContents.Substring(jsonStart, jsonLength); + return JsonSerializer.Deserialize<GenerateServiceWorkerAssetsManifest.AssetsManifestFile>(json); + } + + private static GenerateBlazorBootJson.BootJsonData ReadBootJsonData(MSBuildResult result, string path) + { + return JsonSerializer.Deserialize<GenerateBlazorBootJson.BootJsonData>( + File.ReadAllText(Path.Combine(result.Project.DirectoryPath, path)), + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + } + } +} diff --git a/src/Components/WebAssembly/Build/test/BuildIntegrationTests/PwaManifestTests.cs b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/PwaManifestTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..a268e8f8eacb47f6576600c88d1383ca854737a3 --- /dev/null +++ b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/PwaManifestTests.cs @@ -0,0 +1,45 @@ +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build +{ + public class PwaManifestTests + { + [Fact] + public async Task Build_ServiceWorkerAssetsManifest_Works() + { + // Arrange + var expectedExtensions = new[] { ".dll", ".pdb", ".js", ".wasm" }; + using var project = ProjectDirectory.Create("standalone", additionalProjects: new[] { "razorclasslibrary" }); + var result = await MSBuildProcessManager.DotnetMSBuild(project, args: "/p:ServiceWorkerAssetsManifest=service-worker-assets.js"); + + Assert.BuildPassed(result); + + var buildOutputDirectory = project.BuildOutputDirectory; + + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.boot.json"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "blazor.webassembly.js"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "wasm", "dotnet.wasm"); + Assert.FileCountEquals(result, 1, Path.Combine(buildOutputDirectory, "wwwroot", "_framework", "wasm"), "dotnet.*.js"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "standalone.dll"); + Assert.FileExists(result, buildOutputDirectory, "wwwroot", "_framework", "_bin", "Microsoft.Extensions.Logging.Abstractions.dll"); // Verify dependencies are part of the output. + + var staticWebAssets = Assert.FileExists(result, buildOutputDirectory, "standalone.StaticWebAssets.xml"); + Assert.FileContains(result, staticWebAssets, Path.Combine("netstandard2.1", "wwwroot")); + + var serviceWorkerAssetsManifest = Assert.FileExists(result, buildOutputDirectory, "wwwroot", "service-worker-assets.js"); + // Trim prefix 'self.assetsManifest = ' and suffix ';' + var manifestContents = File.ReadAllText(serviceWorkerAssetsManifest).TrimEnd()[22..^1]; + + var manifestContentsJson = JsonDocument.Parse(manifestContents); + Assert.True(manifestContentsJson.RootElement.TryGetProperty("assets", out var assets)); + Assert.Equal(JsonValueKind.Array, assets.ValueKind); + + var entries = assets.EnumerateArray().Select(e => e.GetProperty("url").GetString()).OrderBy(e => e).ToArray(); + Assert.All(entries, e => expectedExtensions.Contains(Path.GetExtension(e))); + } + } +} diff --git a/src/Components/WebAssembly/Build/test/BuildIntegrationTests/WebAssemblyRuntimePackage.cs b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/WebAssemblyRuntimePackage.cs new file mode 100644 index 0000000000000000000000000000000000000000..d95ba1e41cdd7f954c29f7b812a06fc10b6da4ba --- /dev/null +++ b/src/Components/WebAssembly/Build/test/BuildIntegrationTests/WebAssemblyRuntimePackage.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build +{ + internal static class WebAssemblyRuntimePackage + { + public static readonly string ComponentsWebAssemblyRuntimePackageVersion; + public static readonly string DotNetJsFileName; + + static WebAssemblyRuntimePackage() + { + ComponentsWebAssemblyRuntimePackageVersion = typeof(WebAssemblyRuntimePackage) + .Assembly + .GetCustomAttributes<AssemblyMetadataAttribute>() + .FirstOrDefault(f => f.Key == "Testing.MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion") + ?.Value + ?? throw new InvalidOperationException("Testing.MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion was not found"); + + DotNetJsFileName = $"dotnet.{ComponentsWebAssemblyRuntimePackageVersion}.js"; + } + } +} diff --git a/src/Components/WebAssembly/Build/test/GenerateBlazorBootJsonTest.cs b/src/Components/WebAssembly/Build/test/GenerateBlazorBootJsonTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..5b95548d5796072b27321c53627f5ce0cf968779 --- /dev/null +++ b/src/Components/WebAssembly/Build/test/GenerateBlazorBootJsonTest.cs @@ -0,0 +1,202 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Linq; +using System.Runtime.Serialization.Json; +using Microsoft.Build.Framework; +using Moq; +using Xunit; +using BootJsonData = Microsoft.AspNetCore.Components.WebAssembly.Build.GenerateBlazorBootJson.BootJsonData; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build +{ + public class GenerateBlazorBootJsonTest + { + [Fact] + public void GroupsResourcesByType() + { + // Arrange + var taskInstance = new GenerateBlazorBootJson + { + AssemblyPath = "MyApp.Entrypoint.dll", + Resources = new[] + { + CreateResourceTaskItem( + "assembly", + name: "My.Assembly1.ext", // Can specify filename with no dir + fileHash: "abcdefghikjlmnopqrstuvwxyz"), + + CreateResourceTaskItem( + "assembly", + name: "dir\\My.Assembly2.ext2", // Can specify Windows-style path + fileHash: "012345678901234567890123456789"), + + CreateResourceTaskItem( + "pdb", + name: "otherdir/SomePdb.pdb", // Can specify Linux-style path + fileHash: "pdbhashpdbhashpdbhash"), + + CreateResourceTaskItem( + "pdb", + name: "My.Assembly1.pdb", + fileHash: "pdbdefghikjlmnopqrstuvwxyz"), + + CreateResourceTaskItem( + "runtime", + name: "some-runtime-file", // Can specify path with no extension + fileHash: "runtimehashruntimehash"), + + CreateResourceTaskItem( + "satellite", + name: "en-GB\\satellite-assembly1.ext", + fileHash: "hashsatelliteassembly1", + ("Culture", "en-GB")), + + CreateResourceTaskItem( + "satellite", + name: "fr/satellite-assembly2.dll", + fileHash: "hashsatelliteassembly2", + ("Culture", "fr")), + + CreateResourceTaskItem( + "satellite", + name: "en-GB\\satellite-assembly3.ext", + fileHash: "hashsatelliteassembly3", + ("Culture", "en-GB")), + } + }; + + using var stream = new MemoryStream(); + + // Act + taskInstance.WriteBootJson(stream, "MyEntrypointAssembly"); + + // Assert + var parsedContent = ParseBootData(stream); + Assert.Equal("MyEntrypointAssembly", parsedContent.entryAssembly); + + var resources = parsedContent.resources.assembly; + Assert.Equal(2, resources.Count); + Assert.Equal("sha256-abcdefghikjlmnopqrstuvwxyz", resources["My.Assembly1.ext"]); + Assert.Equal("sha256-012345678901234567890123456789", resources["dir/My.Assembly2.ext2"]); // Paths are converted to use URL-style separators + + resources = parsedContent.resources.pdb; + Assert.Equal(2, resources.Count); + Assert.Equal("sha256-pdbhashpdbhashpdbhash", resources["otherdir/SomePdb.pdb"]); + Assert.Equal("sha256-pdbdefghikjlmnopqrstuvwxyz", resources["My.Assembly1.pdb"]); + + resources = parsedContent.resources.runtime; + Assert.Single(resources); + Assert.Equal("sha256-runtimehashruntimehash", resources["some-runtime-file"]); + + var satelliteResources = parsedContent.resources.satelliteResources; + Assert.Collection( + satelliteResources.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("en-GB", kvp.Key); + Assert.Collection( + kvp.Value.OrderBy(item => item.Key), + item => + { + Assert.Equal("en-GB/satellite-assembly1.ext", item.Key); + Assert.Equal("sha256-hashsatelliteassembly1", item.Value); + }, + item => + { + Assert.Equal("en-GB/satellite-assembly3.ext", item.Key); + Assert.Equal("sha256-hashsatelliteassembly3", item.Value); + }); + }, + kvp => + { + Assert.Equal("fr", kvp.Key); + Assert.Collection( + kvp.Value.OrderBy(item => item.Key), + item => + { + Assert.Equal("fr/satellite-assembly2.dll", item.Key); + Assert.Equal("sha256-hashsatelliteassembly2", item.Value); + }); + }); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CanSpecifyCacheBootResources(bool flagValue) + { + // Arrange + var taskInstance = new GenerateBlazorBootJson { CacheBootResources = flagValue }; + using var stream = new MemoryStream(); + + // Act + taskInstance.WriteBootJson(stream, "MyEntrypointAssembly"); + + // Assert + var parsedContent = ParseBootData(stream); + Assert.Equal(flagValue, parsedContent.cacheBootResources); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CanSpecifyDebugBuild(bool flagValue) + { + // Arrange + var taskInstance = new GenerateBlazorBootJson { DebugBuild = flagValue }; + using var stream = new MemoryStream(); + + // Act + taskInstance.WriteBootJson(stream, "MyEntrypointAssembly"); + + // Assert + var parsedContent = ParseBootData(stream); + Assert.Equal(flagValue, parsedContent.debugBuild); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CanSpecifyLinkerEnabled(bool flagValue) + { + // Arrange + var taskInstance = new GenerateBlazorBootJson { LinkerEnabled = flagValue }; + using var stream = new MemoryStream(); + + // Act + taskInstance.WriteBootJson(stream, "MyEntrypointAssembly"); + + // Assert + var parsedContent = ParseBootData(stream); + Assert.Equal(flagValue, parsedContent.linkerEnabled); + } + + private static BootJsonData ParseBootData(Stream stream) + { + stream.Position = 0; + var serializer = new DataContractJsonSerializer( + typeof(BootJsonData), + new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true }); + return (BootJsonData)serializer.ReadObject(stream); + } + + private static ITaskItem CreateResourceTaskItem(string type, string name, string fileHash, params (string key, string value)[] values) + { + var mock = new Mock<ITaskItem>(); + mock.Setup(m => m.GetMetadata("BootManifestResourceType")).Returns(type); + mock.Setup(m => m.GetMetadata("BootManifestResourceName")).Returns(name); + mock.Setup(m => m.GetMetadata("Integrity")).Returns(fileHash); + + if (values != null) + { + foreach (var (key, value) in values) + { + mock.Setup(m => m.GetMetadata(key)).Returns(value); + } + } + return mock.Object; + } + } +} diff --git a/src/Components/WebAssembly/Build/test/Microsoft.AspNetCore.Components.WebAssembly.Build.Tests.csproj b/src/Components/WebAssembly/Build/test/Microsoft.AspNetCore.Components.WebAssembly.Build.Tests.csproj new file mode 100644 index 0000000000000000000000000000000000000000..035bc4ad6cfdc8fa4e89fc31053ad97390714eea --- /dev/null +++ b/src/Components/WebAssembly/Build/test/Microsoft.AspNetCore.Components.WebAssembly.Build.Tests.csproj @@ -0,0 +1,64 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> + + <!-- Exclude the TestFiles directory from default wildcards --> + <DefaultItemExcludes>$(DefaultItemExcludes);TestFiles\**\*</DefaultItemExcludes> + <NoWarn>$(NoWarn);BL0006</NoWarn> + </PropertyGroup> + + <ItemGroup> + <!-- Embed test files so they can be referenced in tests --> + <EmbeddedResource Include="TestFiles\**" /> + </ItemGroup> + + <PropertyGroup Condition="'$(GenerateBaselines)'=='true'"> + <DefineConstants>GENERATE_BASELINES;$(DefineConstants)</DefineConstants> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> + <DefineConstants>TRACE</DefineConstants> + </PropertyGroup> + + <ItemGroup> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" /> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly.Runtime" /> + <Reference Include="Microsoft.Build.Framework" /> + <Reference Include="Microsoft.Build.Utilities.Core" /> + <Reference Include="Microsoft.Extensions.CommandLineUtils.Sources" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\..\testassets\StandaloneApp\StandaloneApp.csproj" /> + <Compile Include="$(SharedSourceRoot)test\SkipOnHelixAttribute.cs" /> + </ItemGroup> + + <ItemGroup> + <AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute"> + <_Parameter1>Testing.RepoRoot</_Parameter1> + <_Parameter2>$(RepoRoot)</_Parameter2> + </AssemblyAttribute> + + <AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute"> + <_Parameter1>Testing.MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion</_Parameter1> + <_Parameter2>$(MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion)</_Parameter2> + </AssemblyAttribute> + </ItemGroup> + + <Target Name="RestoreTestAssets" AfterTargets="Restore;Build" Condition="'$(DotNetBuildFromSource)' != 'true'"> + <ItemGroup> + <_TestAsset Include="..\testassets\standalone\standalone.csproj" /> + <_TestAsset Include="..\testassets\blazorhosted\blazorhosted.csproj" /> + </ItemGroup> + + <MSBuild + Projects="@(_TestAsset)" + Targets="Restore" + Properties=" + RepoRoot=$(RepoRoot); + MicrosoftNetCompilersToolsetPackageVersion=$(MicrosoftNetCompilersToolsetPackageVersion); + MicrosoftNETSdkRazorPackageVersion=$(MicrosoftNETSdkRazorPackageVersion)" /> + </Target> + +</Project> diff --git a/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs b/src/Components/WebAssembly/Build/test/RuntimeDependenciesResolverTest.cs similarity index 68% rename from src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs rename to src/Components/WebAssembly/Build/test/RuntimeDependenciesResolverTest.cs index 951b1a61df6cd2ec23502b656177138130f403d7..bfcc6b0500b0da96b664b4b09c9a6c67bf84e908 100644 --- a/src/Components/Blazor/Build/test/RuntimeDependenciesResolverTest.cs +++ b/src/Components/WebAssembly/Build/test/RuntimeDependenciesResolverTest.cs @@ -5,39 +5,23 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Text; -using Microsoft.AspNetCore.Testing; using Xunit; -namespace Microsoft.AspNetCore.Blazor.Build +namespace Microsoft.AspNetCore.Components.WebAssembly.Build { public class RuntimeDependenciesResolverTest { - [ConditionalFact(Skip = " https://github.com/aspnet/AspNetCore/issues/12059")] - [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/10426")] + [Fact] public void FindsReferenceAssemblyGraph_ForStandaloneApp() { // Arrange var standaloneAppAssembly = typeof(StandaloneApp.Program).Assembly; var mainAssemblyLocation = standaloneAppAssembly.Location; - var mainAssemblyDirectory = Path.GetDirectoryName(mainAssemblyLocation); - // This list of hints is populated by MSBuild so it will be on the output - // folder. - var hintPaths = File.ReadAllLines(Path.Combine( - mainAssemblyDirectory, "referenceHints.txt")); - var bclLocations = File.ReadAllLines(Path.Combine( - mainAssemblyDirectory, "bclLocations.txt")); - var references = new[] - { - "Microsoft.AspNetCore.Blazor.dll", - "Microsoft.AspNetCore.Components.Web.dll", - "Microsoft.AspNetCore.Components.dll", - "Microsoft.Extensions.DependencyInjection.Abstractions.dll", - "Microsoft.Extensions.DependencyInjection.dll", - "Microsoft.JSInterop.dll", - "Mono.WebAssembly.Interop.dll", - }.Select(a => hintPaths.Single(p => Path.GetFileName(p) == a)) - .ToArray(); + + var hintPaths = ReadContent(standaloneAppAssembly, "StandaloneApp.referenceHints.txt"); + var bclLocations = ReadContent(standaloneAppAssembly, "StandaloneApp.bclLocations.txt"); var expectedContents = new[] { @@ -57,53 +41,64 @@ namespace Microsoft.AspNetCore.Blazor.Build fewer assemblies from the server, and during publishing, illink would remove all the uncalled implementation code from mscorlib.dll anyway. */ - "Microsoft.AspNetCore.Blazor.dll", - "Microsoft.AspNetCore.Blazor.pdb", - "Microsoft.AspNetCore.Components.Web.dll", - "Microsoft.AspNetCore.Components.Web.pdb", "Microsoft.AspNetCore.Components.dll", "Microsoft.AspNetCore.Components.pdb", + "Microsoft.AspNetCore.Components.Forms.dll", + "Microsoft.AspNetCore.Components.Forms.pdb", + "Microsoft.AspNetCore.Components.Web.dll", + "Microsoft.AspNetCore.Components.Web.pdb", + "Microsoft.AspNetCore.Components.WebAssembly.dll", + "Microsoft.AspNetCore.Components.WebAssembly.pdb", + "Microsoft.Bcl.AsyncInterfaces.dll", + "Microsoft.Extensions.Configuration.Abstractions.dll", + "Microsoft.Extensions.Configuration.dll", + "Microsoft.Extensions.Configuration.FileExtensions.dll", + "Microsoft.Extensions.Configuration.Json.dll", "Microsoft.Extensions.DependencyInjection.Abstractions.dll", "Microsoft.Extensions.DependencyInjection.dll", + "Microsoft.Extensions.FileProviders.Abstractions.dll", + "Microsoft.Extensions.FileProviders.Physical.dll", + "Microsoft.Extensions.FileSystemGlobbing.dll", + "Microsoft.Extensions.Logging.dll", + "Microsoft.Extensions.Logging.Abstractions.dll", + "Microsoft.Extensions.Options.dll", + "Microsoft.Extensions.Primitives.dll", "Microsoft.JSInterop.dll", + "Microsoft.JSInterop.WebAssembly.dll", + "Microsoft.JSInterop.WebAssembly.pdb", "Mono.Security.dll", - "Mono.WebAssembly.Interop.dll", "mscorlib.dll", "netstandard.dll", "StandaloneApp.dll", "StandaloneApp.pdb", "System.dll", "System.Buffers.dll", - "System.Collections.Concurrent.dll", - "System.Collections.dll", - "System.ComponentModel.Composition.dll", - "System.ComponentModel.dll", "System.ComponentModel.Annotations.dll", "System.ComponentModel.DataAnnotations.dll", + "System.ComponentModel.Composition.dll", "System.Core.dll", "System.Data.dll", - "System.Diagnostics.Debug.dll", - "System.Diagnostics.Tracing.dll", + "System.Data.DataSetExtensions.dll", "System.Drawing.Common.dll", "System.IO.Compression.dll", "System.IO.Compression.FileSystem.dll", - "System.Linq.dll", - "System.Linq.Expressions.dll", + "System.Memory.dll", "System.Net.Http.dll", + "System.Net.Http.Json.dll", + "System.Net.Http.WebAssemblyHttpHandler.dll", "System.Numerics.dll", - "System.Reflection.Emit.ILGeneration.dll", - "System.Reflection.Emit.Lightweight.dll", - "System.Reflection.Primitives.dll", - "System.Resources.ResourceManager.dll", - "System.Runtime.dll", - "System.Runtime.Extensions.dll", + "System.Numerics.Vectors.dll", + "System.Runtime.CompilerServices.Unsafe.dll", "System.Runtime.Serialization.dll", "System.ServiceModel.Internals.dll", - "System.Threading.dll", + "System.Text.Encodings.Web.dll", + "System.Text.Json.dll", + "System.Threading.Tasks.Extensions.dll", "System.Transactions.dll", - "System.Web.Services.dll", "System.Xml.dll", "System.Xml.Linq.dll", + "WebAssembly.Bindings.dll", + "WebAssembly.Net.WebSockets.dll", }.OrderBy(i => i, StringComparer.Ordinal) .ToArray(); @@ -111,9 +106,9 @@ namespace Microsoft.AspNetCore.Blazor.Build var paths = ResolveBlazorRuntimeDependencies .ResolveRuntimeDependenciesCore( - mainAssemblyLocation, - references, - bclLocations); + mainAssemblyLocation, + hintPaths, + bclLocations); var contents = paths .Select(p => Path.GetFileName(p)) @@ -139,6 +134,14 @@ namespace Microsoft.AspNetCore.Blazor.Build Assert.Equal(expectedContents, contents); } + private string[] ReadContent(Assembly standaloneAppAssembly, string fileName) + { + using var resource = standaloneAppAssembly.GetManifestResourceStream(fileName); + using var streamReader = new StreamReader(resource); + + return streamReader.ReadToEnd().Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); + } + private class ContentMisMatchException : Xunit.Sdk.XunitException { public IEnumerable<string> ContentNotFound { get; set; } diff --git a/src/Components/Blazor/Build/testassets/Directory.Build.props b/src/Components/WebAssembly/Build/testassets/Directory.Build.props similarity index 92% rename from src/Components/Blazor/Build/testassets/Directory.Build.props rename to src/Components/WebAssembly/Build/testassets/Directory.Build.props index cf4b11d0cf4500e3c4e4aa57ed73ce2e648e05f2..495e5ada1518ae4137a7346ed37746d6d79d2816 100644 --- a/src/Components/Blazor/Build/testassets/Directory.Build.props +++ b/src/Components/WebAssembly/Build/testassets/Directory.Build.props @@ -1,10 +1,12 @@ <Project> <Import Project="Before.Directory.Build.props" Condition="Exists('Before.Directory.Build.props')" /> + <!-- Test Placeholder --> + <PropertyGroup> <RepoRoot Condition="'$(RepoRoot)' ==''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), global.json))\</RepoRoot> <ComponentsRoot>$(RepoRoot)src\Components\</ComponentsRoot> - <BlazorBuildRoot>$(ComponentsRoot)Blazor\Build\src\</BlazorBuildRoot> + <BlazorBuildRoot>$(ComponentsRoot)WebAssembly\Build\src\</BlazorBuildRoot> <ReferenceBlazorBuildFromSourceProps>$(BlazorBuildRoot)ReferenceBlazorBuildFromSource.props</ReferenceBlazorBuildFromSourceProps> <!-- Workaround for https://github.com/aspnet/AspNetCore/issues/17308 --> diff --git a/src/Components/Blazor/Build/testassets/Directory.Build.targets b/src/Components/WebAssembly/Build/testassets/Directory.Build.targets similarity index 100% rename from src/Components/Blazor/Build/testassets/Directory.Build.targets rename to src/Components/WebAssembly/Build/testassets/Directory.Build.targets diff --git a/src/Components/Blazor/Build/testassets/razorclasslibrary/wwwroot/styles.css b/src/Components/WebAssembly/Build/testassets/LinkBaseToWebRoot/js/LinkedScript.js similarity index 100% rename from src/Components/Blazor/Build/testassets/razorclasslibrary/wwwroot/styles.css rename to src/Components/WebAssembly/Build/testassets/LinkBaseToWebRoot/js/LinkedScript.js diff --git a/src/Components/Blazor/Build/testassets/blazorhosted/Program.cs b/src/Components/WebAssembly/Build/testassets/blazorhosted/Program.cs similarity index 83% rename from src/Components/Blazor/Build/testassets/blazorhosted/Program.cs rename to src/Components/WebAssembly/Build/testassets/blazorhosted/Program.cs index e2efcc0c7428cf4ed97b371c73c2809004833d50..a90f4db291e14e05117b442b0ccf2e7602268bcb 100644 --- a/src/Components/Blazor/Build/testassets/blazorhosted/Program.cs +++ b/src/Components/WebAssembly/Build/testassets/blazorhosted/Program.cs @@ -10,6 +10,7 @@ namespace blazorhosted.Server public static void Main(string[] args) { Console.WriteLine(typeof(IWebHost)); + GC.KeepAlive(typeof(RazorClassLibrary.Class1)); } } } diff --git a/src/Components/Blazor/Build/testassets/blazorhosted/blazorhosted.csproj b/src/Components/WebAssembly/Build/testassets/blazorhosted/blazorhosted.csproj similarity index 74% rename from src/Components/Blazor/Build/testassets/blazorhosted/blazorhosted.csproj rename to src/Components/WebAssembly/Build/testassets/blazorhosted/blazorhosted.csproj index 5a89588d8c15a7ac889691d62ff35601f7b34ab7..3b08400ff759a7ff5b7d7b6daec80f8f39a556f5 100644 --- a/src/Components/Blazor/Build/testassets/blazorhosted/blazorhosted.csproj +++ b/src/Components/WebAssembly/Build/testassets/blazorhosted/blazorhosted.csproj @@ -2,7 +2,6 @@ <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> - <DisableImplicitComponentsAnalyzers>true</DisableImplicitComponentsAnalyzers> </PropertyGroup> <ItemGroup> diff --git a/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/Class1.cs b/src/Components/WebAssembly/Build/testassets/classlibrarywithsatelliteassemblies/Class1.cs similarity index 100% rename from src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/Class1.cs rename to src/Components/WebAssembly/Build/testassets/classlibrarywithsatelliteassemblies/Class1.cs diff --git a/src/Components/WebAssembly/Build/testassets/classlibrarywithsatelliteassemblies/Resources.es-ES.resx b/src/Components/WebAssembly/Build/testassets/classlibrarywithsatelliteassemblies/Resources.es-ES.resx new file mode 100644 index 0000000000000000000000000000000000000000..0ab8f0ddfb32d7f7c7f49b03bfa82fafc3226e27 --- /dev/null +++ b/src/Components/WebAssembly/Build/testassets/classlibrarywithsatelliteassemblies/Resources.es-ES.resx @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="Hello" xml:space="preserve"> + <value>Hola</value> + </data> +</root> \ No newline at end of file diff --git a/src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj b/src/Components/WebAssembly/Build/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj similarity index 100% rename from src/Components/Blazor/Build/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj rename to src/Components/WebAssembly/Build/testassets/classlibrarywithsatelliteassemblies/classlibrarywithsatelliteassemblies.csproj diff --git a/src/Components/WebAssembly/Build/testassets/razorclasslibrary/Class1.cs b/src/Components/WebAssembly/Build/testassets/razorclasslibrary/Class1.cs new file mode 100644 index 0000000000000000000000000000000000000000..fb55605ff46f95d39e0e21fcc99a4a45b077eb14 --- /dev/null +++ b/src/Components/WebAssembly/Build/testassets/razorclasslibrary/Class1.cs @@ -0,0 +1,6 @@ +namespace RazorClassLibrary +{ + public class Class1 + { + } +} diff --git a/src/Components/Blazor/Build/testassets/razorclasslibrary/RazorClassLibrary.csproj b/src/Components/WebAssembly/Build/testassets/razorclasslibrary/RazorClassLibrary.csproj similarity index 100% rename from src/Components/Blazor/Build/testassets/razorclasslibrary/RazorClassLibrary.csproj rename to src/Components/WebAssembly/Build/testassets/razorclasslibrary/RazorClassLibrary.csproj diff --git a/src/Components/WebAssembly/Build/testassets/razorclasslibrary/wwwroot/styles.css b/src/Components/WebAssembly/Build/testassets/razorclasslibrary/wwwroot/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..5f282702bb03ef11d7184d19c80927b47f919764 --- /dev/null +++ b/src/Components/WebAssembly/Build/testassets/razorclasslibrary/wwwroot/styles.css @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Components/Blazor/Build/testassets/razorclasslibrary/wwwroot/wwwroot/exampleJsInterop.js b/src/Components/WebAssembly/Build/testassets/razorclasslibrary/wwwroot/wwwroot/exampleJsInterop.js similarity index 100% rename from src/Components/Blazor/Build/testassets/razorclasslibrary/wwwroot/wwwroot/exampleJsInterop.js rename to src/Components/WebAssembly/Build/testassets/razorclasslibrary/wwwroot/wwwroot/exampleJsInterop.js diff --git a/src/Components/Blazor/Build/testassets/standalone/App.razor b/src/Components/WebAssembly/Build/testassets/standalone/App.razor similarity index 100% rename from src/Components/Blazor/Build/testassets/standalone/App.razor rename to src/Components/WebAssembly/Build/testassets/standalone/App.razor diff --git a/src/Components/WebAssembly/Build/testassets/standalone/LinkToWebRoot/css/app.css b/src/Components/WebAssembly/Build/testassets/standalone/LinkToWebRoot/css/app.css new file mode 100644 index 0000000000000000000000000000000000000000..6c9631b4e1dd2bb557188e6e9e71306806fcb59d --- /dev/null +++ b/src/Components/WebAssembly/Build/testassets/standalone/LinkToWebRoot/css/app.css @@ -0,0 +1 @@ +.publish { } diff --git a/src/Components/Blazor/Build/testassets/standalone/Pages/Index.razor b/src/Components/WebAssembly/Build/testassets/standalone/Pages/Index.razor similarity index 100% rename from src/Components/Blazor/Build/testassets/standalone/Pages/Index.razor rename to src/Components/WebAssembly/Build/testassets/standalone/Pages/Index.razor diff --git a/src/Components/Blazor/Build/testassets/standalone/Program.cs b/src/Components/WebAssembly/Build/testassets/standalone/Program.cs similarity index 78% rename from src/Components/Blazor/Build/testassets/standalone/Program.cs rename to src/Components/WebAssembly/Build/testassets/standalone/Program.cs index 3e46e6331662e77fcda07f971253d6bbd6fa8327..7d2cb4eeea963ed75fa913ef96f68208331e3881 100644 --- a/src/Components/Blazor/Build/testassets/standalone/Program.cs +++ b/src/Components/WebAssembly/Build/testassets/standalone/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace standalone { @@ -6,6 +6,7 @@ namespace standalone { public static void Main(string[] args) { + GC.KeepAlive(typeof(RazorClassLibrary.Class1)); #if REFERENCE_classlibrarywithsatelliteassemblies GC.KeepAlive(typeof(classlibrarywithsatelliteassemblies.Class1)); #endif diff --git a/src/Components/WebAssembly/Build/testassets/standalone/Resources.ja.resx.txt b/src/Components/WebAssembly/Build/testassets/standalone/Resources.ja.resx.txt new file mode 100644 index 0000000000000000000000000000000000000000..aabe84e3c05313e8f0509eb73c1aec967d6ee9fc --- /dev/null +++ b/src/Components/WebAssembly/Build/testassets/standalone/Resources.ja.resx.txt @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="utf-8"?> +<root> + <!-- + Microsoft ResX Schema + + Version 2.0 + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes + associated with the data types. + + Example: + + ... ado.net/XML headers & schema ... + <resheader name="resmimetype">text/microsoft-resx</resheader> + <resheader name="version">2.0</resheader> + <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> + <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> + <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> + <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> + <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> + <value>[base64 mime encoded serialized .NET Framework object]</value> + </data> + <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> + <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> + <comment>This is a comment</comment> + </data> + + There are any number of "resheader" rows that contain simple + name/value pairs. + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the + mimetype set. + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not + extensible. For a given mimetype the value must be set accordingly: + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can + read any of the formats listed below. + + mimetype: application/x-microsoft.net.object.binary.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.soap.base64 + value : The object must be serialized with + : System.Runtime.Serialization.Formatters.Soap.SoapFormatter + : and then encoded with base64 encoding. + + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> + <xsd:element name="root" msdata:IsDataSet="true"> + <xsd:complexType> + <xsd:choice maxOccurs="unbounded"> + <xsd:element name="metadata"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" /> + </xsd:sequence> + <xsd:attribute name="name" use="required" type="xsd:string" /> + <xsd:attribute name="type" type="xsd:string" /> + <xsd:attribute name="mimetype" type="xsd:string" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="assembly"> + <xsd:complexType> + <xsd:attribute name="alias" type="xsd:string" /> + <xsd:attribute name="name" type="xsd:string" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="data"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> + <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> + <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> + <xsd:attribute ref="xml:space" /> + </xsd:complexType> + </xsd:element> + <xsd:element name="resheader"> + <xsd:complexType> + <xsd:sequence> + <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> + </xsd:sequence> + <xsd:attribute name="name" type="xsd:string" use="required" /> + </xsd:complexType> + </xsd:element> + </xsd:choice> + </xsd:complexType> + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>2.0</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="Hello" xml:space="preserve"> + <value>Konnichiwa</value> + </data> +</root> \ No newline at end of file diff --git a/src/Components/Blazor/Build/testassets/standalone/_Imports.razor b/src/Components/WebAssembly/Build/testassets/standalone/_Imports.razor similarity index 100% rename from src/Components/Blazor/Build/testassets/standalone/_Imports.razor rename to src/Components/WebAssembly/Build/testassets/standalone/_Imports.razor diff --git a/src/Components/WebAssembly/Build/testassets/standalone/standalone.csproj b/src/Components/WebAssembly/Build/testassets/standalone/standalone.csproj new file mode 100644 index 0000000000000000000000000000000000000000..bbeafa77c2121d0fb33c8f99d8e94e4f8afb110c --- /dev/null +++ b/src/Components/WebAssembly/Build/testassets/standalone/standalone.csproj @@ -0,0 +1,40 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + <Import Project="$(ReferenceBlazorBuildFromSourceProps)" /> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + <RazorLangVersion>3.0</RazorLangVersion> + <ServiceWorkerAssetsManifest>custom-service-worker-assets.js</ServiceWorkerAssetsManifest> + </PropertyGroup> + + <!-- Test Placeholder --> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Components" Version="3.1.3" /> + <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Runtime" Version="$(MicrosoftAspNetCoreComponentsWebAssemblyRuntimePackageVersion)" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\razorclasslibrary\RazorClassLibrary.csproj" /> + </ItemGroup> + + <ItemGroup> + <!-- These assets should be treated as static web assets for publish purposes --> + <Content Include="..\LinkBaseToWebRoot\**\*.js"> + <LinkBase>wwwroot\</LinkBase> + </Content> + + <!-- This asset should be ignored as a static web assets as it defines CopyToPublishDirectory="false" --> + <Content Update="wwwroot\css\app.css" CopyToPublishDirectory="false" /> + + <!-- This asset should be treated as a static web asset and copied into the right location defined by its link attribute. --> + <Content Include="LinkToWebRoot\css\app.css" Link="wwwroot\css\app.css" /> + + <!-- This asset should not be treated as a static web asset as it is being linked out of the wwwroot folder. --> + <Content Update="wwwroot\Fake-License.txt" Link="Excluded-Static-Web-Assets\Fake-License.txt" /> + + <!-- The content from my-prod-service-worker.js should be published under the name my-service-worker.js --> + <ServiceWorker Include="wwwroot\serviceworkers\my-service-worker.js" PublishedContent="wwwroot\serviceworkers\my-prod-service-worker.js" /> + </ItemGroup> + +</Project> diff --git a/src/Components/WebAssembly/Build/testassets/standalone/wwwroot/Fake-License.txt b/src/Components/WebAssembly/Build/testassets/standalone/wwwroot/Fake-License.txt new file mode 100644 index 0000000000000000000000000000000000000000..5f282702bb03ef11d7184d19c80927b47f919764 --- /dev/null +++ b/src/Components/WebAssembly/Build/testassets/standalone/wwwroot/Fake-License.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Components/WebAssembly/Build/testassets/standalone/wwwroot/css/app.css b/src/Components/WebAssembly/Build/testassets/standalone/wwwroot/css/app.css new file mode 100644 index 0000000000000000000000000000000000000000..fc64a1237602a173a9bc93df04596eea385e6c69 --- /dev/null +++ b/src/Components/WebAssembly/Build/testassets/standalone/wwwroot/css/app.css @@ -0,0 +1 @@ +.build { } diff --git a/src/Components/Blazor/Build/testassets/standalone/wwwroot/index.html b/src/Components/WebAssembly/Build/testassets/standalone/wwwroot/index.html similarity index 91% rename from src/Components/Blazor/Build/testassets/standalone/wwwroot/index.html rename to src/Components/WebAssembly/Build/testassets/standalone/wwwroot/index.html index 85994d6e89fed5cabbc16969c5cb972c8dd3476c..bbfa66c41abcbc20107d2b54dfe77de0d01bff28 100644 --- a/src/Components/Blazor/Build/testassets/standalone/wwwroot/index.html +++ b/src/Components/WebAssembly/Build/testassets/standalone/wwwroot/index.html @@ -7,7 +7,7 @@ <title>standalone</title> <base href="/" /> <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" /> - <link href="css/site.css" rel="stylesheet" /> + <link href="css/app.css" rel="stylesheet" /> </head> <body> diff --git a/src/Components/WebAssembly/Build/testassets/standalone/wwwroot/serviceworkers/my-prod-service-worker.js b/src/Components/WebAssembly/Build/testassets/standalone/wwwroot/serviceworkers/my-prod-service-worker.js new file mode 100644 index 0000000000000000000000000000000000000000..a2ecc1b349c238784618bdda371fbbb5253e8ef4 --- /dev/null +++ b/src/Components/WebAssembly/Build/testassets/standalone/wwwroot/serviceworkers/my-prod-service-worker.js @@ -0,0 +1 @@ +// This is the production service worker diff --git a/src/Components/WebAssembly/Build/testassets/standalone/wwwroot/serviceworkers/my-service-worker.js b/src/Components/WebAssembly/Build/testassets/standalone/wwwroot/serviceworkers/my-service-worker.js new file mode 100644 index 0000000000000000000000000000000000000000..c42d1c84755f05d5aaff00bad4363ab52af4a5a5 --- /dev/null +++ b/src/Components/WebAssembly/Build/testassets/standalone/wwwroot/serviceworkers/my-service-worker.js @@ -0,0 +1 @@ +// This is the development service worker diff --git a/src/Components/WebAssembly/Compression/src/Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj b/src/Components/WebAssembly/Compression/src/Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj new file mode 100644 index 0000000000000000000000000000000000000000..c959d4ad2dacf4a2dc45ae8e9bb5911dc4a27e68 --- /dev/null +++ b/src/Components/WebAssembly/Compression/src/Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> + <OutputType>Exe</OutputType> + <AssemblyName>blazor-brotli</AssemblyName> + <IsShippingPackage>false</IsShippingPackage> + <HasReferenceAssembly>false</HasReferenceAssembly> + <MicrosoftNETCoreAppRuntimeVersion>3.1.0</MicrosoftNETCoreAppRuntimeVersion> + </PropertyGroup> + +</Project> diff --git a/src/Components/WebAssembly/Compression/src/Program.cs b/src/Components/WebAssembly/Compression/src/Program.cs new file mode 100644 index 0000000000000000000000000000000000000000..3391e960367fdd60ff057add01b4ea4e5c560002 --- /dev/null +++ b/src/Components/WebAssembly/Compression/src/Program.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Build.BrotliCompression +{ + class Program + { + private const int _error = -1; + + static async Task<int> Main(string[] args) + { + if (args.Length != 1) + { + Console.Error.WriteLine("Invalid argument count. Usage: 'blazor-brotli <<path-to-manifest>>'"); + return _error; + } + + var manifestPath = args[0]; + if (!File.Exists(manifestPath)) + { + Console.Error.WriteLine($"Manifest '{manifestPath}' does not exist."); + return -1; + } + + using var manifestStream = File.OpenRead(manifestPath); + + var manifest = await JsonSerializer.DeserializeAsync<ManifestData>(manifestStream); + var result = 0; + Parallel.ForEach(manifest.FilesToCompress, (file) => + { + var inputPath = file.Source; + var inputSource = file.InputSource; + var targetCompressionPath = file.Target; + + if (!File.Exists(inputSource)) + { + Console.WriteLine($"Skipping '{inputPath}' because '{inputSource}' does not exist."); + return; + } + + if (File.Exists(targetCompressionPath) && File.GetLastWriteTimeUtc(inputSource) < File.GetLastWriteTimeUtc(targetCompressionPath)) + { + // Incrementalism. If input source doesn't exist or it exists and is not newer than the expected output, do nothing. + Console.WriteLine($"Skipping '{inputPath}' because '{targetCompressionPath}' is newer than '{inputSource}'."); + return; + } + + try + { + Directory.CreateDirectory(Path.GetDirectoryName(targetCompressionPath)); + + using var sourceStream = File.OpenRead(inputPath); + using var fileStream = new FileStream(targetCompressionPath, FileMode.Create); + using var stream = new BrotliStream(fileStream, CompressionLevel.Optimal); + + sourceStream.CopyTo(stream); + } + catch (Exception e) + { + Console.Error.WriteLine(e); + result = -1; + } + }); + + return result; + } + + private class ManifestData + { + public CompressedFile[] FilesToCompress { get; set; } + } + + private class CompressedFile + { + public string Source { get; set; } + + public string InputSource { get; set; } + + public string Target { get; set; } + } + } +} diff --git a/src/Components/WebAssembly/Compression/src/runtimeconfig.template.json b/src/Components/WebAssembly/Compression/src/runtimeconfig.template.json new file mode 100644 index 0000000000000000000000000000000000000000..f022b7ffce12faf836b7ce0f45660109445c5d51 --- /dev/null +++ b/src/Components/WebAssembly/Compression/src/runtimeconfig.template.json @@ -0,0 +1,3 @@ +{ + "rollForwardOnNoCandidateFx": 2 +} \ No newline at end of file diff --git a/src/Components/WebAssembly/DebugProxy/src/DebugProxyOptions.cs b/src/Components/WebAssembly/DebugProxy/src/DebugProxyOptions.cs new file mode 100644 index 0000000000000000000000000000000000000000..70a76258c6cfb5bc3ed3e993857be76d3351cc09 --- /dev/null +++ b/src/Components/WebAssembly/DebugProxy/src/DebugProxyOptions.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.DebugProxy +{ + public class DebugProxyOptions + { + public string BrowserHost { get; set; } + } +} diff --git a/src/Components/WebAssembly/DebugProxy/src/Hosting/DebugProxyHost.cs b/src/Components/WebAssembly/DebugProxy/src/Hosting/DebugProxyHost.cs new file mode 100644 index 0000000000000000000000000000000000000000..8a710028a0ca51e4a1de2a18f09cacc31851a65d --- /dev/null +++ b/src/Components/WebAssembly/DebugProxy/src/Hosting/DebugProxyHost.cs @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.Hosting +{ + public static class DebugProxyHost + { + /// <summary> + /// Creates a custom HostBuilder for the DebugProxyLauncher so that we can inject + /// only the needed configurations. + /// </summary> + /// <param name="args">Command line arguments passed in</param> + /// <param name="browserHost">Host where browser is listening for debug connections</param> + /// <returns><see cref="IHostBuilder"></returns> + public static IHostBuilder CreateDefaultBuilder(string[] args, string browserHost) + { + var builder = new HostBuilder(); + + builder.ConfigureAppConfiguration((hostingContext, config) => + { + if (args != null) + { + config.AddCommandLine(args); + } + config.AddJsonFile("blazor-debugproxysettings.json", optional: true, reloadOnChange: true); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + logging.AddDebug(); + logging.AddEventSourceLogger(); + }) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup<Startup>(); + + // By default we bind to a dyamic port + // This can be overridden using an option like "--urls http://localhost:9500" + webBuilder.UseUrls("http://127.0.0.1:0"); + }) + .ConfigureServices(serviceCollection => + { + serviceCollection.AddSingleton(new DebugProxyOptions + { + BrowserHost = browserHost + }); + }); + + return builder; + + } + } +} diff --git a/src/Components/WebAssembly/DebugProxy/src/Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj b/src/Components/WebAssembly/DebugProxy/src/Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj new file mode 100644 index 0000000000000000000000000000000000000000..af1e79c007f65d81bd3abd69b0caf72fb3fc592e --- /dev/null +++ b/src/Components/WebAssembly/DebugProxy/src/Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj @@ -0,0 +1,27 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> + <OutputType>Exe</OutputType> + <PackageId>Microsoft.AspNetCore.Components.WebAssembly.DebugProxy</PackageId> + <IsShippingPackage>true</IsShippingPackage> + <HasReferenceAssembly>false</HasReferenceAssembly> + <Description>Debug proxy for use when building Blazor applications.</Description> + <!-- Set this to false because assemblies should not reference this assembly directly, (except for tests, of course). --> + <IsProjectReferenceProvider>false</IsProjectReferenceProvider> + <NoWarn>$(NoWarn);CS0649</NoWarn> + </PropertyGroup> + + <ItemGroup> + <Reference Include="Microsoft.AspNetCore" /> + <Reference Include="Microsoft.AspNetCore.WebSockets" /> + <Reference Include="Microsoft.Extensions.CommandLineUtils.Sources" /> + <Reference Include="Microsoft.Extensions.Hosting" /> + + <!-- Dependencies of ws-proxy sources --> + <Reference Include="Newtonsoft.Json" /> + <Reference Include="Mono.Cecil" /> + <Reference Include="Microsoft.CodeAnalysis.CSharp" /> + </ItemGroup> + +</Project> diff --git a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DebugStore.cs b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DebugStore.cs new file mode 100644 index 0000000000000000000000000000000000000000..1b9568de804aeb775fa08b7dd10a765caa6b7415 --- /dev/null +++ b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DebugStore.cs @@ -0,0 +1,843 @@ +using System; +using System.IO; +using System.Collections.Generic; +using Mono.Cecil; +using Mono.Cecil.Cil; +using System.Linq; +using Newtonsoft.Json.Linq; +using System.Net.Http; +using Mono.Cecil.Pdb; +using Newtonsoft.Json; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Threading; +using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; + +namespace WebAssembly.Net.Debugging { + internal class BreakpointRequest { + public string Id { get; private set; } + public string Assembly { get; private set; } + public string File { get; private set; } + public int Line { get; private set; } + public int Column { get; private set; } + public MethodInfo Method { get; private set; } + + JObject request; + + public bool IsResolved => Assembly != null; + public List<Breakpoint> Locations { get; } = new List<Breakpoint> (); + + public override string ToString () + => $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}"; + + public object AsSetBreakpointByUrlResponse (IEnumerable<object> jsloc) + => new { breakpointId = Id, locations = Locations.Select(l => l.Location.AsLocation ()).Concat (jsloc) }; + + public BreakpointRequest () { + } + + public BreakpointRequest (string id, MethodInfo method) { + Id = id; + Method = method; + } + + public BreakpointRequest (string id, JObject request) { + Id = id; + this.request = request; + } + + public static BreakpointRequest Parse (string id, JObject args) + { + return new BreakpointRequest (id, args); + } + + public BreakpointRequest Clone () + => new BreakpointRequest { Id = Id, request = request }; + + public bool IsMatch (SourceFile sourceFile) + { + var url = request? ["url"]?.Value<string> (); + if (url == null) { + var urlRegex = request?["urlRegex"].Value<string>(); + var regex = new Regex (urlRegex); + return regex.IsMatch (sourceFile.Url.ToString ()) || regex.IsMatch (sourceFile.DocUrl); + } + + return sourceFile.Url.ToString () == url || sourceFile.DotNetUrl == url; + } + + public bool TryResolve (SourceFile sourceFile) + { + if (!IsMatch (sourceFile)) + return false; + + var line = request? ["lineNumber"]?.Value<int> (); + var column = request? ["columnNumber"]?.Value<int> (); + + if (line == null || column == null) + return false; + + Assembly = sourceFile.AssemblyName; + File = sourceFile.DebuggerFileName; + Line = line.Value; + Column = column.Value; + return true; + } + + public bool TryResolve (DebugStore store) + { + if (request == null || store == null) + return false; + + return store.AllSources().FirstOrDefault (source => TryResolve (source)) != null; + } + } + + internal class VarInfo { + public VarInfo (VariableDebugInformation v) + { + this.Name = v.Name; + this.Index = v.Index; + } + + public VarInfo (ParameterDefinition p) + { + this.Name = p.Name; + this.Index = (p.Index + 1) * -1; + } + + public string Name { get; } + public int Index { get; } + + public override string ToString () + => $"(var-info [{Index}] '{Name}')"; + } + + internal class CliLocation { + public CliLocation (MethodInfo method, int offset) + { + Method = method; + Offset = offset; + } + + public MethodInfo Method { get; } + public int Offset { get; } + } + + internal class SourceLocation { + SourceId id; + int line; + int column; + CliLocation cliLoc; + + public SourceLocation (SourceId id, int line, int column) + { + this.id = id; + this.line = line; + this.column = column; + } + + public SourceLocation (MethodInfo mi, SequencePoint sp) + { + this.id = mi.SourceId; + this.line = sp.StartLine - 1; + this.column = sp.StartColumn - 1; + this.cliLoc = new CliLocation (mi, sp.Offset); + } + + public SourceId Id { get => id; } + public int Line { get => line; } + public int Column { get => column; } + public CliLocation CliLocation => this.cliLoc; + + public override string ToString () + => $"{id}:{Line}:{Column}"; + + public static SourceLocation Parse (JObject obj) + { + if (obj == null) + return null; + + if (!SourceId.TryParse (obj ["scriptId"]?.Value<string> (), out var id)) + return null; + + var line = obj ["lineNumber"]?.Value<int> (); + var column = obj ["columnNumber"]?.Value<int> (); + if (id == null || line == null || column == null) + return null; + + return new SourceLocation (id, line.Value, column.Value); + } + + + internal class LocationComparer : EqualityComparer<SourceLocation> + { + public override bool Equals (SourceLocation l1, SourceLocation l2) + { + if (l1 == null && l2 == null) + return true; + else if (l1 == null || l2 == null) + return false; + + return (l1.Line == l2.Line && + l1.Column == l2.Column && + l1.Id == l2.Id); + } + + public override int GetHashCode (SourceLocation loc) + { + int hCode = loc.Line ^ loc.Column; + return loc.Id.GetHashCode () ^ hCode.GetHashCode (); + } + } + + internal object AsLocation () + => new { + scriptId = id.ToString (), + lineNumber = line, + columnNumber = column + }; + } + + internal class SourceId { + const string Scheme = "dotnet://"; + + readonly int assembly, document; + + public int Assembly => assembly; + public int Document => document; + + internal SourceId (int assembly, int document) + { + this.assembly = assembly; + this.document = document; + } + + public SourceId (string id) + { + if (!TryParse (id, out assembly, out document)) + throw new ArgumentException ("invalid source identifier", nameof (id)); + } + + public static bool TryParse (string id, out SourceId source) + { + source = null; + if (!TryParse (id, out var assembly, out var document)) + return false; + + source = new SourceId (assembly, document); + return true; + } + + static bool TryParse (string id, out int assembly, out int document) + { + assembly = document = 0; + if (id == null || !id.StartsWith (Scheme, StringComparison.Ordinal)) + return false; + + var sp = id.Substring (Scheme.Length).Split ('_'); + if (sp.Length != 2) + return false; + + if (!int.TryParse (sp [0], out assembly)) + return false; + + if (!int.TryParse (sp [1], out document)) + return false; + + return true; + } + + public override string ToString () + => $"{Scheme}{assembly}_{document}"; + + public override bool Equals (object obj) + { + if (obj == null) + return false; + SourceId that = obj as SourceId; + return that.assembly == this.assembly && that.document == this.document; + } + + public override int GetHashCode () + => assembly.GetHashCode () ^ document.GetHashCode (); + + public static bool operator == (SourceId a, SourceId b) + => ((object)a == null) ? (object)b == null : a.Equals (b); + + public static bool operator != (SourceId a, SourceId b) + => !a.Equals (b); + } + + internal class MethodInfo { + MethodDefinition methodDef; + SourceFile source; + + public SourceId SourceId => source.SourceId; + + public string Name => methodDef.Name; + public MethodDebugInformation DebugInformation => methodDef.DebugInformation; + + public SourceLocation StartLocation { get; } + public SourceLocation EndLocation { get; } + public AssemblyInfo Assembly { get; } + public uint Token => methodDef.MetadataToken.RID; + + public MethodInfo (AssemblyInfo assembly, MethodDefinition methodDef, SourceFile source) + { + this.Assembly = assembly; + this.methodDef = methodDef; + this.source = source; + + var sps = DebugInformation.SequencePoints; + if (sps == null || sps.Count() < 1) + return; + + SequencePoint start = sps [0]; + SequencePoint end = sps [0]; + + foreach (var sp in sps) { + if (sp.StartLine < start.StartLine) + start = sp; + else if (sp.StartLine == start.StartLine && sp.StartColumn < start.StartColumn) + start = sp; + + if (sp.EndLine > end.EndLine) + end = sp; + else if (sp.EndLine == end.EndLine && sp.EndColumn > end.EndColumn) + end = sp; + } + + StartLocation = new SourceLocation (this, start); + EndLocation = new SourceLocation (this, end); + } + + public SourceLocation GetLocationByIl (int pos) + { + SequencePoint prev = null; + foreach (var sp in DebugInformation.SequencePoints) { + if (sp.Offset > pos) + break; + prev = sp; + } + + if (prev != null) + return new SourceLocation (this, prev); + + return null; + } + + public VarInfo [] GetLiveVarsAt (int offset) + { + var res = new List<VarInfo> (); + + res.AddRange (methodDef.Parameters.Select (p => new VarInfo (p))); + res.AddRange (methodDef.DebugInformation.GetScopes () + .Where (s => s.Start.Offset <= offset && (s.End.IsEndOfMethod || s.End.Offset > offset)) + .SelectMany (s => s.Variables) + .Where (v => !v.IsDebuggerHidden) + .Select (v => new VarInfo (v))); + + return res.ToArray (); + } + + public override string ToString () => "MethodInfo(" + methodDef.FullName + ")"; + } + + internal class TypeInfo { + AssemblyInfo assembly; + TypeDefinition type; + List<MethodInfo> methods; + + public TypeInfo (AssemblyInfo assembly, TypeDefinition type) { + this.assembly = assembly; + this.type = type; + methods = new List<MethodInfo> (); + } + + public string Name => type.Name; + public string FullName => type.FullName; + public List<MethodInfo> Methods => methods; + + public override string ToString () => "TypeInfo('" + FullName + "')"; + } + + class AssemblyInfo { + static int next_id; + ModuleDefinition image; + readonly int id; + readonly ILogger logger; + Dictionary<uint, MethodInfo> methods = new Dictionary<uint, MethodInfo> (); + Dictionary<string, string> sourceLinkMappings = new Dictionary<string, string>(); + Dictionary<string, TypeInfo> typesByName = new Dictionary<string, TypeInfo> (); + readonly List<SourceFile> sources = new List<SourceFile>(); + internal string Url { get; } + + public AssemblyInfo (string url, byte[] assembly, byte[] pdb) + { + this.id = Interlocked.Increment (ref next_id); + + try { + Url = url; + ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/); + + // set ReadSymbols = true unconditionally in case there + // is an embedded pdb then handle ArgumentException + // and assume that if pdb == null that is the cause + rp.ReadSymbols = true; + rp.SymbolReaderProvider = new PdbReaderProvider (); + if (pdb != null) + rp.SymbolStream = new MemoryStream (pdb); + rp.ReadingMode = ReadingMode.Immediate; + rp.InMemory = true; + + this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp); + } catch (BadImageFormatException ex) { + logger.LogWarning ($"Failed to read assembly as portable PDB: {ex.Message}"); + } catch (ArgumentException) { + // if pdb == null this is expected and we + // read the assembly without symbols below + if (pdb != null) + throw; + } + + if (this.image == null) { + ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/); + if (pdb != null) { + rp.ReadSymbols = true; + rp.SymbolReaderProvider = new PdbReaderProvider (); + rp.SymbolStream = new MemoryStream (pdb); + } + + rp.ReadingMode = ReadingMode.Immediate; + rp.InMemory = true; + + this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp); + } + + Populate (); + } + + public AssemblyInfo (ILogger logger) + { + this.logger = logger; + } + + void Populate () + { + ProcessSourceLink(); + + var d2s = new Dictionary<Document, SourceFile> (); + + SourceFile FindSource (Document doc) + { + if (doc == null) + return null; + + if (d2s.TryGetValue (doc, out var source)) + return source; + + var src = new SourceFile (this, sources.Count, doc, GetSourceLinkUrl (doc.Url)); + sources.Add (src); + d2s [doc] = src; + return src; + }; + + foreach (var type in image.GetTypes()) { + var typeInfo = new TypeInfo (this, type); + typesByName [type.FullName] = typeInfo; + + foreach (var method in type.Methods) { + foreach (var sp in method.DebugInformation.SequencePoints) { + var source = FindSource (sp.Document); + var methodInfo = new MethodInfo (this, method, source); + methods [method.MetadataToken.RID] = methodInfo; + if (source != null) + source.AddMethod (methodInfo); + + typeInfo.Methods.Add (methodInfo); + } + } + } + } + + private void ProcessSourceLink () + { + var sourceLinkDebugInfo = image.CustomDebugInformations.FirstOrDefault (i => i.Kind == CustomDebugInformationKind.SourceLink); + + if (sourceLinkDebugInfo != null) { + var sourceLinkContent = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content; + + if (sourceLinkContent != null) { + var jObject = JObject.Parse (sourceLinkContent) ["documents"]; + sourceLinkMappings = JsonConvert.DeserializeObject<Dictionary<string, string>> (jObject.ToString ()); + } + } + } + + private Uri GetSourceLinkUrl (string document) + { + if (sourceLinkMappings.TryGetValue (document, out string url)) + return new Uri (url); + + foreach (var sourceLinkDocument in sourceLinkMappings) { + string key = sourceLinkDocument.Key; + + if (Path.GetFileName (key) != "*") { + continue; + } + + var keyTrim = key.TrimEnd ('*'); + + if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase)) { + var docUrlPart = document.Replace (keyTrim, ""); + return new Uri (sourceLinkDocument.Value.TrimEnd ('*') + docUrlPart); + } + } + + return null; + } + + private static string GetRelativePath (string relativeTo, string path) + { + var uri = new Uri (relativeTo, UriKind.RelativeOrAbsolute); + var rel = Uri.UnescapeDataString (uri.MakeRelativeUri (new Uri (path, UriKind.RelativeOrAbsolute)).ToString ()).Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); + if (rel.Contains (Path.DirectorySeparatorChar.ToString ()) == false) { + rel = $".{ Path.DirectorySeparatorChar }{ rel }"; + } + return rel; + } + + public IEnumerable<SourceFile> Sources + => this.sources; + + public int Id => id; + public string Name => image.Name; + + public SourceFile GetDocById (int document) + { + return sources.FirstOrDefault (s => s.SourceId.Document == document); + } + + public MethodInfo GetMethodByToken (uint token) + { + methods.TryGetValue (token, out var value); + return value; + } + + public TypeInfo GetTypeByName (string name) { + typesByName.TryGetValue (name, out var res); + return res; + } + } + + internal class SourceFile { + Dictionary<uint, MethodInfo> methods; + AssemblyInfo assembly; + int id; + Document doc; + + internal SourceFile (AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri) + { + this.methods = new Dictionary<uint, MethodInfo> (); + this.SourceLinkUri = sourceLinkUri; + this.assembly = assembly; + this.id = id; + this.doc = doc; + this.DebuggerFileName = doc.Url.Replace ("\\", "/").Replace (":", ""); + + this.SourceUri = new Uri ((Path.IsPathRooted (doc.Url) ? "file://" : "") + doc.Url, UriKind.RelativeOrAbsolute); + if (SourceUri.IsFile && File.Exists (SourceUri.LocalPath)) { + this.Url = this.SourceUri.ToString (); + } else { + this.Url = DotNetUrl; + } + } + + internal void AddMethod (MethodInfo mi) + { + if (!this.methods.ContainsKey (mi.Token)) + this.methods [mi.Token] = mi; + } + + public string DebuggerFileName { get; } + public string Url { get; } + public string AssemblyName => assembly.Name; + public string DotNetUrl => $"dotnet://{assembly.Name}/{DebuggerFileName}"; + + public SourceId SourceId => new SourceId (assembly.Id, this.id); + public Uri SourceLinkUri { get; } + public Uri SourceUri { get; } + + public IEnumerable<MethodInfo> Methods => this.methods.Values; + + public string DocUrl => doc.Url; + + public (int startLine, int startColumn, int endLine, int endColumn) GetExtents () + { + var start = Methods.OrderBy (m => m.StartLocation.Line).ThenBy (m => m.StartLocation.Column).First (); + var end = Methods.OrderByDescending (m => m.EndLocation.Line).ThenByDescending (m => m.EndLocation.Column).First (); + return (start.StartLocation.Line, start.StartLocation.Column, end.EndLocation.Line, end.EndLocation.Column); + } + + async Task<MemoryStream> GetDataAsync (Uri uri, CancellationToken token) + { + var mem = new MemoryStream (); + try { + if (uri.IsFile && File.Exists (uri.LocalPath)) { + using (var file = File.Open (SourceUri.LocalPath, FileMode.Open)) { + await file.CopyToAsync (mem, token); + mem.Position = 0; + } + } else if (uri.Scheme == "http" || uri.Scheme == "https") { + var client = new HttpClient (); + using (var stream = await client.GetStreamAsync (uri)) { + await stream.CopyToAsync (mem, token); + mem.Position = 0; + } + } + } catch (Exception) { + return null; + } + return mem; + } + + static HashAlgorithm GetHashAlgorithm (DocumentHashAlgorithm algorithm) + { + switch (algorithm) { + case DocumentHashAlgorithm.SHA1: return SHA1.Create (); + case DocumentHashAlgorithm.SHA256: return SHA256.Create (); + case DocumentHashAlgorithm.MD5: return MD5.Create (); + } + return null; + } + + bool CheckPdbHash (byte [] computedHash) + { + if (computedHash.Length != doc.Hash.Length) + return false; + + for (var i = 0; i < computedHash.Length; i++) + if (computedHash[i] != doc.Hash[i]) + return false; + + return true; + } + + byte[] ComputePdbHash (Stream sourceStream) + { + var algorithm = GetHashAlgorithm (doc.HashAlgorithm); + if (algorithm != null) + using (algorithm) + return algorithm.ComputeHash (sourceStream); + + return Array.Empty<byte> (); + } + + public async Task<Stream> GetSourceAsync (bool checkHash, CancellationToken token = default(CancellationToken)) + { + if (doc.EmbeddedSource.Length > 0) + return new MemoryStream (doc.EmbeddedSource, false); + + MemoryStream mem; + + mem = await GetDataAsync (SourceUri, token); + if (mem != null && (!checkHash || CheckPdbHash (ComputePdbHash (mem)))) { + mem.Position = 0; + return mem; + } + + mem = await GetDataAsync (SourceLinkUri, token); + if (mem != null && (!checkHash || CheckPdbHash (ComputePdbHash (mem)))) { + mem.Position = 0; + return mem; + } + + return MemoryStream.Null; + } + + public object ToScriptSource (int executionContextId, object executionContextAuxData) + { + return new { + scriptId = SourceId.ToString (), + url = Url, + executionContextId, + executionContextAuxData, + //hash: should be the v8 hash algo, managed implementation is pending + dotNetUrl = DotNetUrl, + }; + } + } + + internal class DebugStore { + List<AssemblyInfo> assemblies = new List<AssemblyInfo> (); + readonly HttpClient client; + readonly ILogger logger; + + public DebugStore (ILogger logger, HttpClient client) { + this.client = client; + this.logger = logger; + } + + public DebugStore (ILogger logger) : this (logger, new HttpClient ()) + { + } + + class DebugItem { + public string Url { get; set; } + public Task<byte[][]> Data { get; set; } + } + + public async IAsyncEnumerable<SourceFile> Load (SessionId sessionId, string [] loaded_files, [EnumeratorCancellation] CancellationToken token) + { + static bool MatchPdb (string asm, string pdb) + => Path.ChangeExtension (asm, "pdb") == pdb; + + var asm_files = new List<string> (); + var pdb_files = new List<string> (); + foreach (var file_name in loaded_files) { + if (file_name.EndsWith (".pdb", StringComparison.OrdinalIgnoreCase)) + pdb_files.Add (file_name); + else + asm_files.Add (file_name); + } + + List<DebugItem> steps = new List<DebugItem> (); + foreach (var url in asm_files) { + try { + var pdb = pdb_files.FirstOrDefault (n => MatchPdb (url, n)); + steps.Add ( + new DebugItem { + Url = url, + Data = Task.WhenAll (client.GetByteArrayAsync (url), pdb != null ? client.GetByteArrayAsync (pdb) : Task.FromResult<byte []> (null)) + }); + } catch (Exception e) { + logger.LogDebug ($"Failed to read {url} ({e.Message})"); + } + } + + foreach (var step in steps) { + AssemblyInfo assembly = null; + try { + var bytes = await step.Data; + assembly = new AssemblyInfo (step.Url, bytes [0], bytes [1]); + } catch (Exception e) { + logger.LogDebug ($"Failed to load {step.Url} ({e.Message})"); + } + if (assembly == null) + continue; + + assemblies.Add (assembly); + foreach (var source in assembly.Sources) + yield return source; + } + } + + public IEnumerable<SourceFile> AllSources () + => assemblies.SelectMany (a => a.Sources); + + public SourceFile GetFileById (SourceId id) + => AllSources ().SingleOrDefault (f => f.SourceId.Equals (id)); + + public AssemblyInfo GetAssemblyByName (string name) + => assemblies.FirstOrDefault (a => a.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase)); + + /* + V8 uses zero based indexing for both line and column. + PPDBs uses one based indexing for both line and column. + */ + static bool Match (SequencePoint sp, SourceLocation start, SourceLocation end) + { + var spStart = (Line: sp.StartLine - 1, Column: sp.StartColumn - 1); + var spEnd = (Line: sp.EndLine - 1, Column: sp.EndColumn - 1); + + if (start.Line > spEnd.Line) + return false; + + if (start.Column > spEnd.Column && start.Line == spEnd.Line) + return false; + + if (end.Line < spStart.Line) + return false; + + if (end.Column < spStart.Column && end.Line == spStart.Line) + return false; + + return true; + } + + public List<SourceLocation> FindPossibleBreakpoints (SourceLocation start, SourceLocation end) + { + //XXX FIXME no idea what todo with locations on different files + if (start.Id != end.Id) { + logger.LogDebug ($"FindPossibleBreakpoints: documents differ (start: {start.Id}) (end {end.Id}"); + return null; + } + + var sourceId = start.Id; + + var doc = GetFileById (sourceId); + + var res = new List<SourceLocation> (); + if (doc == null) { + logger.LogDebug ($"Could not find document {sourceId}"); + return res; + } + + foreach (var method in doc.Methods) { + foreach (var sequencePoint in method.DebugInformation.SequencePoints) { + if (!sequencePoint.IsHidden && Match (sequencePoint, start, end)) + res.Add (new SourceLocation (method, sequencePoint)); + } + } + return res; + } + + /* + V8 uses zero based indexing for both line and column. + PPDBs uses one based indexing for both line and column. + */ + static bool Match (SequencePoint sp, int line, int column) + { + var bp = (line: line + 1, column: column + 1); + + if (sp.StartLine > bp.line || sp.EndLine < bp.line) + return false; + + //Chrome sends a zero column even if getPossibleBreakpoints say something else + if (column == 0) + return true; + + if (sp.StartColumn > bp.column && sp.StartLine == bp.line) + return false; + + if (sp.EndColumn < bp.column && sp.EndLine == bp.line) + return false; + + return true; + } + + public IEnumerable<SourceLocation> FindBreakpointLocations (BreakpointRequest request) + { + request.TryResolve (this); + + var asm = assemblies.FirstOrDefault (a => a.Name.Equals (request.Assembly, StringComparison.OrdinalIgnoreCase)); + var sourceFile = asm?.Sources?.SingleOrDefault (s => s.DebuggerFileName.Equals (request.File, StringComparison.OrdinalIgnoreCase)); + + if (sourceFile == null) + yield break; + + foreach (var method in sourceFile.Methods) { + foreach (var sequencePoint in method.DebugInformation.SequencePoints) { + if (!sequencePoint.IsHidden && Match (sequencePoint, request.Line, request.Column)) + yield return new SourceLocation (method, sequencePoint); + } + } + } + + public string ToUrl (SourceLocation location) + => location != null ? GetFileById (location.Id).Url : ""; + } +} diff --git a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsHelper.cs b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..871c10de9b01d9356a43b27d7c5f04c6918af1fc --- /dev/null +++ b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsHelper.cs @@ -0,0 +1,298 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +using System.Threading; +using System.IO; +using System.Collections.Generic; +using System.Net; +using Microsoft.Extensions.Logging; + +namespace WebAssembly.Net.Debugging { + + internal struct SessionId { + public readonly string sessionId; + + public SessionId (string sessionId) + { + this.sessionId = sessionId; + } + + // hashset treats 0 as unset + public override int GetHashCode () + => sessionId?.GetHashCode () ?? -1; + + public override bool Equals (object obj) + => (obj is SessionId) ? ((SessionId) obj).sessionId == sessionId : false; + + public static bool operator == (SessionId a, SessionId b) + => a.sessionId == b.sessionId; + + public static bool operator != (SessionId a, SessionId b) + => a.sessionId != b.sessionId; + + public static SessionId Null { get; } = new SessionId (); + + public override string ToString () + => $"session-{sessionId}"; + } + + internal struct MessageId { + public readonly string sessionId; + public readonly int id; + + public MessageId (string sessionId, int id) + { + this.sessionId = sessionId; + this.id = id; + } + + public static implicit operator SessionId (MessageId id) + => new SessionId (id.sessionId); + + public override string ToString () + => $"msg-{sessionId}:::{id}"; + + public override int GetHashCode () + => (sessionId?.GetHashCode () ?? 0) ^ id.GetHashCode (); + + public override bool Equals (object obj) + => (obj is MessageId) ? ((MessageId) obj).sessionId == sessionId && ((MessageId) obj).id == id : false; + } + + internal class DotnetObjectId { + public string Scheme { get; } + public string Value { get; } + + public static bool TryParse (JToken jToken, out DotnetObjectId objectId) + => TryParse (jToken?.Value<string>(), out objectId); + + public static bool TryParse (string id, out DotnetObjectId objectId) + { + objectId = null; + if (id == null) + return false; + + if (!id.StartsWith ("dotnet:")) + return false; + + var parts = id.Split (":", 3); + + if (parts.Length < 3) + return false; + + objectId = new DotnetObjectId (parts[1], parts[2]); + + return true; + } + + public DotnetObjectId (string scheme, string value) + { + Scheme = scheme; + Value = value; + } + + public override string ToString () + => $"dotnet:{Scheme}:{Value}"; + } + + internal struct Result { + public JObject Value { get; private set; } + public JObject Error { get; private set; } + + public bool IsOk => Value != null; + public bool IsErr => Error != null; + + Result (JObject result, JObject error) + { + if (result != null && error != null) + throw new ArgumentException ($"Both {nameof(result)} and {nameof(error)} arguments cannot be non-null."); + + bool resultHasError = String.Compare ((result? ["result"] as JObject)? ["subtype"]?. Value<string> (), "error") == 0; + if (result != null && resultHasError) { + this.Value = null; + this.Error = result; + } else { + this.Value = result; + this.Error = error; + } + } + + public static Result FromJson (JObject obj) + { + //Log ("protocol", $"from result: {obj}"); + return new Result (obj ["result"] as JObject, obj ["error"] as JObject); + } + + public static Result Ok (JObject ok) + => new Result (ok, null); + + public static Result OkFromObject (object ok) + => Ok (JObject.FromObject(ok)); + + public static Result Err (JObject err) + => new Result (null, err); + + public static Result Err (string msg) + => new Result (null, JObject.FromObject (new { message = msg })); + + public static Result Exception (Exception e) + => new Result (null, JObject.FromObject (new { message = e.Message })); + + public JObject ToJObject (MessageId target) { + if (IsOk) { + return JObject.FromObject (new { + target.id, + target.sessionId, + result = Value + }); + } else { + return JObject.FromObject (new { + target.id, + target.sessionId, + error = Error + }); + } + } + + public override string ToString () + { + return $"[Result: IsOk: {IsOk}, IsErr: {IsErr}, Value: {Value?.ToString ()}, Error: {Error?.ToString ()} ]"; + } + } + + internal class MonoCommands { + public string expression { get; set; } + public string objectGroup { get; set; } = "mono-debugger"; + public bool includeCommandLineAPI { get; set; } = false; + public bool silent { get; set; } = false; + public bool returnByValue { get; set; } = true; + + public MonoCommands (string expression) + => this.expression = expression; + + public static MonoCommands GetCallStack () + => new MonoCommands ("MONO.mono_wasm_get_call_stack()"); + + public static MonoCommands IsRuntimeReady () + => new MonoCommands ("MONO.mono_wasm_runtime_is_ready"); + + public static MonoCommands StartSingleStepping (StepKind kind) + => new MonoCommands ($"MONO.mono_wasm_start_single_stepping ({(int)kind})"); + + public static MonoCommands GetLoadedFiles () + => new MonoCommands ("MONO.mono_wasm_get_loaded_files()"); + + public static MonoCommands ClearAllBreakpoints () + => new MonoCommands ("MONO.mono_wasm_clear_all_breakpoints()"); + + public static MonoCommands GetDetails (DotnetObjectId objectId, JToken args = null) + => new MonoCommands ($"MONO.mono_wasm_get_details ('{objectId}', {(args ?? "{}")})"); + + public static MonoCommands GetScopeVariables (int scopeId, params int[] vars) + => new MonoCommands ($"MONO.mono_wasm_get_variables({scopeId}, [ {string.Join (",", vars)} ])"); + + public static MonoCommands SetBreakpoint (string assemblyName, uint methodToken, int ilOffset) + => new MonoCommands ($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})"); + + public static MonoCommands RemoveBreakpoint (int breakpointId) + => new MonoCommands ($"MONO.mono_wasm_remove_breakpoint({breakpointId})"); + + public static MonoCommands ReleaseObject (DotnetObjectId objectId) + => new MonoCommands ($"MONO.mono_wasm_release_object('{objectId}')"); + + public static MonoCommands CallFunctionOn (JToken args) + => new MonoCommands ($"MONO.mono_wasm_call_function_on ({args.ToString ()})"); + } + + internal enum MonoErrorCodes { + BpNotFound = 100000, + } + + internal class MonoConstants { + public const string RUNTIME_IS_READY = "mono_wasm_runtime_ready"; + } + + class Frame { + public Frame (MethodInfo method, SourceLocation location, int id) + { + this.Method = method; + this.Location = location; + this.Id = id; + } + + public MethodInfo Method { get; private set; } + public SourceLocation Location { get; private set; } + public int Id { get; private set; } + } + + class Breakpoint { + public SourceLocation Location { get; private set; } + public int RemoteId { get; set; } + public BreakpointState State { get; set; } + public string StackId { get; private set; } + + public static bool TryParseId (string stackId, out int id) + { + id = -1; + if (stackId?.StartsWith ("dotnet:", StringComparison.Ordinal) != true) + return false; + + return int.TryParse (stackId.Substring ("dotnet:".Length), out id); + } + + public Breakpoint (string stackId, SourceLocation loc, BreakpointState state) + { + this.StackId = stackId; + this.Location = loc; + this.State = state; + } + } + + enum BreakpointState { + Active, + Disabled, + Pending + } + + enum StepKind { + Into, + Out, + Over + } + + internal class ExecutionContext { + public string DebuggerId { get; set; } + public Dictionary<string,BreakpointRequest> BreakpointRequests { get; } = new Dictionary<string,BreakpointRequest> (); + + public TaskCompletionSource<DebugStore> ready = null; + public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted; + + public int Id { get; set; } + public object AuxData { get; set; } + + public List<Frame> CallStack { get; set; } + + internal DebugStore store; + public TaskCompletionSource<DebugStore> Source { get; } = new TaskCompletionSource<DebugStore> (); + + public Dictionary<string, JToken> LocalsCache = new Dictionary<string, JToken> (); + + public DebugStore Store { + get { + if (store == null || !Source.Task.IsCompleted) + return null; + + return store; + } + } + + public void ClearState () + { + CallStack = null; + LocalsCache.Clear (); + } + + } +} diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/WsProxy.cs b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsProxy.cs similarity index 50% rename from src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/WsProxy.cs rename to src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsProxy.cs index 87ef23027e8a0fd86535aacf16bfd5708b696321..1cc711809a3a18c189050ead426b4db4d6fe61d7 100644 --- a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/WsProxy.cs +++ b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsProxy.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Linq; using System.Threading.Tasks; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Net.WebSockets; @@ -8,59 +9,17 @@ using System.Threading; using System.IO; using System.Text; using System.Collections.Generic; +using Microsoft.Extensions.Logging; -namespace WsProxy { +namespace WebAssembly.Net.Debugging { - internal struct Result { - public JObject Value { get; private set; } - public JObject Error { get; private set; } - - public bool IsOk => Value != null; - public bool IsErr => Error != null; - - Result (JObject result, JObject error) - { - this.Value = result; - this.Error = error; - } - - public static Result FromJson (JObject obj) - { - return new Result (obj ["result"] as JObject, obj ["error"] as JObject); - } - - public static Result Ok (JObject ok) - { - return new Result (ok, null); - } - - public static Result Err (JObject err) - { - return new Result (null, err); - } - - public JObject ToJObject (int id) { - if (IsOk) { - return JObject.FromObject (new { - id = id, - result = Value - }); - } else { - return JObject.FromObject (new { - id = id, - error = Error - }); - } - } - } - - class WsQueue { + class DevToolsQueue { Task current_send; List<byte []> pending; public WebSocket Ws { get; private set; } public Task CurrentSend { get { return current_send; } } - public WsQueue (WebSocket sock) + public DevToolsQueue (WebSocket sock) { this.Ws = sock; pending = new List<byte []> (); @@ -71,8 +30,8 @@ namespace WsProxy { pending.Add (bytes); if (pending.Count == 1) { if (current_send != null) - throw new Exception ("UNEXPECTED, current_send MUST BE NULL IF THERE'S no pending send"); - //Console.WriteLine ("sending {0} bytes", bytes.Length); + throw new Exception ("current_send MUST BE NULL IF THERE'S no pending send"); + //logger.LogTrace ("sending {0} bytes", bytes.Length); current_send = Ws.SendAsync (new ArraySegment<byte> (bytes), WebSocketMessageType.Text, true, token); return current_send; } @@ -86,8 +45,8 @@ namespace WsProxy { if (pending.Count > 0) { if (current_send != null) - throw new Exception ("UNEXPECTED, current_send MUST BE NULL IF THERE'S no pending send"); - //Console.WriteLine ("sending more {0} bytes", pending[0].Length); + throw new Exception ("current_send MUST BE NULL IF THERE'S no pending send"); + current_send = Ws.SendAsync (new ArraySegment<byte> (pending [0]), WebSocketMessageType.Text, true, token); return current_send; } @@ -95,22 +54,29 @@ namespace WsProxy { } } - internal class WsProxy { + internal class DevToolsProxy { TaskCompletionSource<bool> side_exception = new TaskCompletionSource<bool> (); TaskCompletionSource<bool> client_initiated_close = new TaskCompletionSource<bool> (); - List<(int, TaskCompletionSource<Result>)> pending_cmds = new List<(int, TaskCompletionSource<Result>)> (); + Dictionary<MessageId, TaskCompletionSource<Result>> pending_cmds = new Dictionary<MessageId, TaskCompletionSource<Result>> (); ClientWebSocket browser; WebSocket ide; int next_cmd_id; List<Task> pending_ops = new List<Task> (); - List<WsQueue> queues = new List<WsQueue> (); + List<DevToolsQueue> queues = new List<DevToolsQueue> (); + + protected readonly ILogger logger; + + public DevToolsProxy (ILoggerFactory loggerFactory) + { + logger = loggerFactory.CreateLogger<DevToolsProxy>(); + } - protected virtual Task<bool> AcceptEvent (string method, JObject args, CancellationToken token) + protected virtual Task<bool> AcceptEvent (SessionId sessionId, string method, JObject args, CancellationToken token) { return Task.FromResult (false); } - protected virtual Task<bool> AcceptCommand (int id, string method, JObject args, CancellationToken token) + protected virtual Task<bool> AcceptCommand (MessageId id, string method, JObject args, CancellationToken token) { return Task.FromResult (false); } @@ -122,7 +88,7 @@ namespace WsProxy { while (true) { if (socket.State != WebSocketState.Open) { - Console.WriteLine ($"WSProxy: Socket is no longer open."); + Log ("error", $"DevToolsProxy: Socket is no longer open."); client_initiated_close.TrySetResult (true); return null; } @@ -133,51 +99,56 @@ namespace WsProxy { return null; } - if (result.EndOfMessage) { - mem.Write (buff, 0, result.Count); + mem.Write (buff, 0, result.Count); + + if (result.EndOfMessage) return Encoding.UTF8.GetString (mem.GetBuffer (), 0, (int)mem.Length); - } else { - mem.Write (buff, 0, result.Count); - } } } - WsQueue GetQueueForSocket (WebSocket ws) + DevToolsQueue GetQueueForSocket (WebSocket ws) { return queues.FirstOrDefault (q => q.Ws == ws); } - WsQueue GetQueueForTask (Task task) { + DevToolsQueue GetQueueForTask (Task task) + { return queues.FirstOrDefault (q => q.CurrentSend == task); } void Send (WebSocket to, JObject o, CancellationToken token) { - var bytes = Encoding.UTF8.GetBytes (o.ToString ()); + var sender = browser == to ? "Send-browser" : "Send-ide"; + + var method = o ["method"]?.ToString (); + //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled") + Log ("protocol", $"{sender}: " + JsonConvert.SerializeObject (o)); + var bytes = Encoding.UTF8.GetBytes (o.ToString ()); var queue = GetQueueForSocket (to); + var task = queue.Send (bytes, token); if (task != null) pending_ops.Add (task); } - async Task OnEvent (string method, JObject args, CancellationToken token) + async Task OnEvent (SessionId sessionId, string method, JObject args, CancellationToken token) { try { - if (!await AcceptEvent (method, args, token)) { - //Console.WriteLine ("proxy browser: {0}::{1}",method, args); - SendEventInternal (method, args, token); + if (!await AcceptEvent (sessionId, method, args, token)) { + //logger.LogDebug ("proxy browser: {0}::{1}",method, args); + SendEventInternal (sessionId, method, args, token); } } catch (Exception e) { side_exception.TrySetException (e); } } - async Task OnCommand (int id, string method, JObject args, CancellationToken token) + async Task OnCommand (MessageId id, string method, JObject args, CancellationToken token) { try { if (!await AcceptCommand (id, method, args, token)) { - var res = await SendCommandInternal (method, args, token); + var res = await SendCommandInternal (id, method, args, token); SendResponseInternal (id, res, token); } } catch (Exception e) { @@ -185,82 +156,97 @@ namespace WsProxy { } } - void OnResponse (int id, Result result) + void OnResponse (MessageId id, Result result) { - //Console.WriteLine ("got id {0} res {1}", id, result); - var idx = pending_cmds.FindIndex (e => e.Item1 == id); - var item = pending_cmds [idx]; - pending_cmds.RemoveAt (idx); - - item.Item2.SetResult (result); + //logger.LogTrace ("got id {0} res {1}", id, result); + // Fixme + if (pending_cmds.Remove (id, out var task)) { + task.SetResult (result); + return; + } + logger.LogError ("Cannot respond to command: {id} with result: {result} - command is not pending", id, result); } void ProcessBrowserMessage (string msg, CancellationToken token) { - // Debug ($"browser: {msg}"); var res = JObject.Parse (msg); + var method = res ["method"]?.ToString (); + //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled") + Log ("protocol", $"browser: {msg}"); + if (res ["id"] == null) - pending_ops.Add (OnEvent (res ["method"].Value<string> (), res ["params"] as JObject, token)); + pending_ops.Add (OnEvent (new SessionId (res ["sessionId"]?.Value<string> ()), res ["method"].Value<string> (), res ["params"] as JObject, token)); else - OnResponse (res ["id"].Value<int> (), Result.FromJson (res)); + OnResponse (new MessageId (res ["sessionId"]?.Value<string> (), res ["id"].Value<int> ()), Result.FromJson (res)); } void ProcessIdeMessage (string msg, CancellationToken token) { + Log ("protocol", $"ide: {msg}"); if (!string.IsNullOrEmpty (msg)) { var res = JObject.Parse (msg); - pending_ops.Add (OnCommand (res ["id"].Value<int> (), res ["method"].Value<string> (), res ["params"] as JObject, token)); + pending_ops.Add (OnCommand ( + new MessageId (res ["sessionId"]?.Value<string> (), res ["id"].Value<int> ()), + res ["method"].Value<string> (), + res ["params"] as JObject, token)); } } - internal async Task<Result> SendCommand (string method, JObject args, CancellationToken token) { - // Debug ($"sending command {method}: {args}"); - return await SendCommandInternal (method, args, token); + internal async Task<Result> SendCommand (SessionId id, string method, JObject args, CancellationToken token) { + //Log ("verbose", $"sending command {method}: {args}"); + return await SendCommandInternal (id, method, args, token); } - Task<Result> SendCommandInternal (string method, JObject args, CancellationToken token) + Task<Result> SendCommandInternal (SessionId sessionId, string method, JObject args, CancellationToken token) { - int id = ++next_cmd_id; + int id = Interlocked.Increment (ref next_cmd_id); var o = JObject.FromObject (new { - id = id, - method = method, + id, + method, @params = args }); + if (sessionId.sessionId != null) + o["sessionId"] = sessionId.sessionId; var tcs = new TaskCompletionSource<Result> (); - //Console.WriteLine ("add cmd id {0}", id); - pending_cmds.Add ((id, tcs)); + + var msgId = new MessageId (sessionId.sessionId, id); + //Log ("verbose", $"add cmd id {sessionId}-{id}"); + pending_cmds[msgId] = tcs; Send (this.browser, o, token); return tcs.Task; } - public void SendEvent (string method, JObject args, CancellationToken token) + public void SendEvent (SessionId sessionId, string method, JObject args, CancellationToken token) { - //Debug ($"sending event {method}: {args}"); - SendEventInternal (method, args, token); + //Log ("verbose", $"sending event {method}: {args}"); + SendEventInternal (sessionId, method, args, token); } - void SendEventInternal (string method, JObject args, CancellationToken token) + void SendEventInternal (SessionId sessionId, string method, JObject args, CancellationToken token) { var o = JObject.FromObject (new { - method = method, + method, @params = args }); + if (sessionId.sessionId != null) + o["sessionId"] = sessionId.sessionId; Send (this.ide, o, token); } - internal void SendResponse (int id, Result result, CancellationToken token) + internal void SendResponse (MessageId id, Result result, CancellationToken token) { - //Debug ($"sending response: {id}: {result.ToJObject (id)}"); SendResponseInternal (id, result, token); } - void SendResponseInternal (int id, Result result, CancellationToken token) + void SendResponseInternal (MessageId id, Result result, CancellationToken token) { JObject o = result.ToJObject (id); + if (result.IsErr) + logger.LogError ($"sending error response for id: {id} -> {result}"); Send (this.ide, o, token); } @@ -268,16 +254,16 @@ namespace WsProxy { // , HttpContext context) public async Task Run (Uri browserUri, WebSocket ideSocket) { - Debug ($"WsProxy Starting on {browserUri}"); + Log ("info", $"DevToolsProxy: Starting on {browserUri}"); using (this.ide = ideSocket) { - Debug ($"WsProxy: IDE waiting for connection on {browserUri}"); - queues.Add (new WsQueue (this.ide)); + Log ("verbose", $"DevToolsProxy: IDE waiting for connection on {browserUri}"); + queues.Add (new DevToolsQueue (this.ide)); using (this.browser = new ClientWebSocket ()) { this.browser.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan; await this.browser.ConnectAsync (browserUri, CancellationToken.None); - queues.Add (new WsQueue (this.browser)); + queues.Add (new DevToolsQueue (this.browser)); - Debug ($"WsProxy: Client connected on {browserUri}"); + Log ("verbose", $"DevToolsProxy: Client connected on {browserUri}"); var x = new CancellationTokenSource (); pending_ops.Add (ReadOne (browser, x.Token)); @@ -288,7 +274,7 @@ namespace WsProxy { try { while (!x.IsCancellationRequested) { var task = await Task.WhenAny (pending_ops.ToArray ()); - //Console.WriteLine ("pump {0} {1}", task, pending_ops.IndexOf (task)); + //logger.LogTrace ("pump {0} {1}", task, pending_ops.IndexOf (task)); if (task == pending_ops [0]) { var msg = ((Task<string>)task).Result; if (msg != null) { @@ -306,7 +292,7 @@ namespace WsProxy { throw new Exception ("side task must always complete with an exception, what's going on???"); } else if (task == pending_ops [3]) { var res = ((Task<bool>)task).Result; - Debug ($"WsProxy: Client initiated close from {browserUri}"); + Log ("verbose", $"DevToolsProxy: Client initiated close from {browserUri}"); x.Cancel (); } else { //must be a background task @@ -320,7 +306,7 @@ namespace WsProxy { } } } catch (Exception e) { - Debug ($"WsProxy::Run: Exception {e}"); + Log ("error", $"DevToolsProxy::Run: Exception {e}"); //throw; } finally { if (!x.IsCancellationRequested) @@ -330,14 +316,22 @@ namespace WsProxy { } } - protected void Debug (string msg) + protected void Log (string priority, string msg) { - Console.WriteLine (msg); - } - - protected void Info (string msg) - { - Console.WriteLine (msg); + switch (priority) { + case "protocol": + //logger.LogTrace (msg); + break; + case "verbose": + //logger.LogDebug (msg); + break; + case "info": + case "warning": + case "error": + default: + logger.LogDebug (msg); + break; + } } } } diff --git a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/EvaluateExpression.cs b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/EvaluateExpression.cs new file mode 100644 index 0000000000000000000000000000000000000000..fb7e776ed90249359805a37de2778822bcb3ac10 --- /dev/null +++ b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/EvaluateExpression.cs @@ -0,0 +1,182 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +using System.Threading; +using System.IO; +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Emit; +using System.Reflection; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace WebAssembly.Net.Debugging { + + internal class EvaluateExpression { + + class FindThisExpression : CSharpSyntaxWalker { + public List<string> thisExpressions = new List<string> (); + public SyntaxTree syntaxTree; + public FindThisExpression (SyntaxTree syntax) + { + syntaxTree = syntax; + } + public override void Visit (SyntaxNode node) + { + if (node is ThisExpressionSyntax) { + if (node.Parent is MemberAccessExpressionSyntax thisParent && thisParent.Name is IdentifierNameSyntax) { + IdentifierNameSyntax var = thisParent.Name as IdentifierNameSyntax; + thisExpressions.Add(var.Identifier.Text); + var newRoot = syntaxTree.GetRoot ().ReplaceNode (node.Parent, thisParent.Name); + syntaxTree = syntaxTree.WithRootAndOptions (newRoot, syntaxTree.Options); + this.Visit (GetExpressionFromSyntaxTree(syntaxTree)); + } + } + else + base.Visit (node); + } + + public async Task CheckIfIsProperty (MonoProxy proxy, MessageId msg_id, int scope_id, CancellationToken token) + { + foreach (var var in thisExpressions) { + JToken value = await proxy.TryGetVariableValue (msg_id, scope_id, var, true, token); + if (value == null) + throw new Exception ($"The property {var} does not exist in the current context"); + } + } + } + + class FindVariableNMethodCall : CSharpSyntaxWalker { + public List<IdentifierNameSyntax> variables = new List<IdentifierNameSyntax> (); + public List<ThisExpressionSyntax> thisList = new List<ThisExpressionSyntax> (); + public List<InvocationExpressionSyntax> methodCall = new List<InvocationExpressionSyntax> (); + public List<object> values = new List<Object> (); + + public override void Visit (SyntaxNode node) + { + if (node is IdentifierNameSyntax identifier && !variables.Any (x => x.Identifier.Text == identifier.Identifier.Text)) + variables.Add (identifier); + if (node is InvocationExpressionSyntax) { + methodCall.Add (node as InvocationExpressionSyntax); + throw new Exception ("Method Call is not implemented yet"); + } + if (node is AssignmentExpressionSyntax) + throw new Exception ("Assignment is not implemented yet"); + base.Visit (node); + } + public async Task<SyntaxTree> ReplaceVars (SyntaxTree syntaxTree, MonoProxy proxy, MessageId msg_id, int scope_id, CancellationToken token) + { + CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot (); + foreach (var var in variables) { + ClassDeclarationSyntax classDeclaration = root.Members.ElementAt (0) as ClassDeclarationSyntax; + MethodDeclarationSyntax method = classDeclaration.Members.ElementAt (0) as MethodDeclarationSyntax; + + JToken value = await proxy.TryGetVariableValue (msg_id, scope_id, var.Identifier.Text, false, token); + + if (value == null) + throw new Exception ($"The name {var.Identifier.Text} does not exist in the current context"); + + values.Add (ConvertJSToCSharpType (value ["value"] ["value"].ToString (), value ["value"] ["type"].ToString ())); + + var updatedMethod = method.AddParameterListParameters ( + SyntaxFactory.Parameter ( + SyntaxFactory.Identifier (var.Identifier.Text)) + .WithType (SyntaxFactory.ParseTypeName (GetTypeFullName(value["value"]["type"].ToString())))); + root = root.ReplaceNode (method, updatedMethod); + } + syntaxTree = syntaxTree.WithRootAndOptions (root, syntaxTree.Options); + return syntaxTree; + } + + private object ConvertJSToCSharpType (string v, string type) + { + switch (type) { + case "number": + return Convert.ChangeType (v, typeof (int)); + case "string": + return v; + } + + throw new Exception ($"Evaluate of this datatype {type} not implemented yet"); + } + + private string GetTypeFullName (string type) + { + switch (type) { + case "number": + return typeof (int).FullName; + case "string": + return typeof (string).FullName; + } + + throw new Exception ($"Evaluate of this datatype {type} not implemented yet"); + } + } + static SyntaxNode GetExpressionFromSyntaxTree (SyntaxTree syntaxTree) + { + CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot (); + ClassDeclarationSyntax classDeclaration = root.Members.ElementAt (0) as ClassDeclarationSyntax; + MethodDeclarationSyntax methodDeclaration = classDeclaration.Members.ElementAt (0) as MethodDeclarationSyntax; + BlockSyntax blockValue = methodDeclaration.Body; + ReturnStatementSyntax returnValue = blockValue.Statements.ElementAt (0) as ReturnStatementSyntax; + InvocationExpressionSyntax expressionInvocation = returnValue.Expression as InvocationExpressionSyntax; + MemberAccessExpressionSyntax expressionMember = expressionInvocation.Expression as MemberAccessExpressionSyntax; + ParenthesizedExpressionSyntax expressionParenthesized = expressionMember.Expression as ParenthesizedExpressionSyntax; + return expressionParenthesized.Expression; + } + internal static async Task<string> CompileAndRunTheExpression (MonoProxy proxy, MessageId msg_id, int scope_id, string expression, CancellationToken token) + { + FindVariableNMethodCall findVarNMethodCall = new FindVariableNMethodCall (); + string retString; + SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText (@" + using System; + public class CompileAndRunTheExpression + { + public string Evaluate() + { + return (" + expression + @").ToString(); + } + }"); + + FindThisExpression findThisExpression = new FindThisExpression (syntaxTree); + var expressionTree = GetExpressionFromSyntaxTree(syntaxTree); + findThisExpression.Visit (expressionTree); + await findThisExpression.CheckIfIsProperty (proxy, msg_id, scope_id, token); + syntaxTree = findThisExpression.syntaxTree; + + expressionTree = GetExpressionFromSyntaxTree (syntaxTree); + findVarNMethodCall.Visit (expressionTree); + + syntaxTree = await findVarNMethodCall.ReplaceVars (syntaxTree, proxy, msg_id, scope_id, token); + + MetadataReference [] references = new MetadataReference [] + { + MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location) + }; + + CSharpCompilation compilation = CSharpCompilation.Create ( + "compileAndRunTheExpression", + syntaxTrees: new [] { syntaxTree }, + references: references, + options: new CSharpCompilationOptions (OutputKind.DynamicallyLinkedLibrary)); + using (var ms = new MemoryStream ()) { + EmitResult result = compilation.Emit (ms); + ms.Seek (0, SeekOrigin.Begin); + Assembly assembly = Assembly.Load (ms.ToArray ()); + Type type = assembly.GetType ("CompileAndRunTheExpression"); + object obj = Activator.CreateInstance (type); + var ret = type.InvokeMember ("Evaluate", + BindingFlags.Default | BindingFlags.InvokeMethod, + null, + obj, + //new object [] { 10 } + findVarNMethodCall.values.ToArray ()); + retString = ret.ToString (); + } + return retString; + } + } +} diff --git a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/MonoProxy.cs b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/MonoProxy.cs new file mode 100644 index 0000000000000000000000000000000000000000..1b26dd93fd40271b04e03a4ec74460cf0cdc9135 --- /dev/null +++ b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/MonoProxy.cs @@ -0,0 +1,885 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +using System.Threading; +using System.IO; +using System.Collections.Generic; +using System.Net; +using Microsoft.Extensions.Logging; +using Microsoft.CodeAnalysis; + + +namespace WebAssembly.Net.Debugging { + + internal class MonoProxy : DevToolsProxy { + HashSet<SessionId> sessions = new HashSet<SessionId> (); + Dictionary<SessionId, ExecutionContext> contexts = new Dictionary<SessionId, ExecutionContext> (); + + public MonoProxy (ILoggerFactory loggerFactory, bool hideWebDriver = true) : base(loggerFactory) { this.hideWebDriver = hideWebDriver; } + + readonly bool hideWebDriver; + + internal ExecutionContext GetContext (SessionId sessionId) + { + if (contexts.TryGetValue (sessionId, out var context)) + return context; + + throw new ArgumentException ($"Invalid Session: \"{sessionId}\"", nameof (sessionId)); + } + + bool UpdateContext (SessionId sessionId, ExecutionContext executionContext, out ExecutionContext previousExecutionContext) + { + var previous = contexts.TryGetValue (sessionId, out previousExecutionContext); + contexts[sessionId] = executionContext; + return previous; + } + + internal Task<Result> SendMonoCommand (SessionId id, MonoCommands cmd, CancellationToken token) + => SendCommand (id, "Runtime.evaluate", JObject.FromObject (cmd), token); + + protected override async Task<bool> AcceptEvent (SessionId sessionId, string method, JObject args, CancellationToken token) + { + switch (method) { + case "Runtime.consoleAPICalled": { + var type = args["type"]?.ToString (); + if (type == "debug") { + if (args["args"]?[0]?["value"]?.ToString () == MonoConstants.RUNTIME_IS_READY && args["args"]?[1]?["value"]?.ToString () == "fe00e07a-5519-4dfe-b35a-f867dbaf2e28") + await RuntimeReady (sessionId, token); + } + break; + } + + case "Runtime.executionContextCreated": { + SendEvent (sessionId, method, args, token); + var ctx = args? ["context"]; + var aux_data = ctx? ["auxData"] as JObject; + var id = ctx ["id"].Value<int> (); + if (aux_data != null) { + var is_default = aux_data ["isDefault"]?.Value<bool> (); + if (is_default == true) { + await OnDefaultContext (sessionId, new ExecutionContext { Id = id, AuxData = aux_data }, token); + } + } + return true; + } + + case "Debugger.paused": { + //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack + var top_func = args? ["callFrames"]? [0]? ["functionName"]?.Value<string> (); + + if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp") { + return await OnBreakpointHit (sessionId, args, token); + } + break; + } + + case "Debugger.breakpointResolved": { + break; + } + + case "Debugger.scriptParsed": { + var url = args? ["url"]?.Value<string> () ?? ""; + + switch (url) { + case var _ when url == "": + case var _ when url.StartsWith ("wasm://", StringComparison.Ordinal): { + Log ("verbose", $"ignoring wasm: Debugger.scriptParsed {url}"); + return true; + } + } + Log ("verbose", $"proxying Debugger.scriptParsed ({sessionId.sessionId}) {url} {args}"); + break; + } + + case "Target.attachedToTarget": { + if (args["targetInfo"]["type"]?.ToString() == "page") + await DeleteWebDriver (new SessionId (args["sessionId"]?.ToString ()), token); + break; + } + + } + + return false; + } + + async Task<bool> IsRuntimeAlreadyReadyAlready (SessionId sessionId, CancellationToken token) + { + var res = await SendMonoCommand (sessionId, MonoCommands.IsRuntimeReady (), token); + return res.Value? ["result"]? ["value"]?.Value<bool> () ?? false; + } + + static int bpIdGenerator; + + protected override async Task<bool> AcceptCommand (MessageId id, string method, JObject args, CancellationToken token) + { + // Inspector doesn't use the Target domain or sessions + // so we try to init immediately + if (hideWebDriver && id == SessionId.Null) + await DeleteWebDriver (id, token); + + if (!contexts.TryGetValue (id, out var context)) + return false; + + switch (method) { + case "Target.attachToTarget": { + var resp = await SendCommand (id, method, args, token); + await DeleteWebDriver (new SessionId (resp.Value ["sessionId"]?.ToString ()), token); + break; + } + + case "Debugger.enable": { + var resp = await SendCommand (id, method, args, token); + + context.DebuggerId = resp.Value ["debuggerId"]?.ToString (); + + if (await IsRuntimeAlreadyReadyAlready (id, token)) + await RuntimeReady (id, token); + + SendResponse (id,resp,token); + return true; + } + + case "Debugger.getScriptSource": { + var script = args? ["scriptId"]?.Value<string> (); + return await OnGetScriptSource (id, script, token); + } + + case "Runtime.compileScript": { + var exp = args? ["expression"]?.Value<string> (); + if (exp.StartsWith ("//dotnet:", StringComparison.Ordinal)) { + OnCompileDotnetScript (id, token); + return true; + } + break; + } + + case "Debugger.getPossibleBreakpoints": { + var resp = await SendCommand (id, method, args, token); + if (resp.IsOk && resp.Value["locations"].HasValues) { + SendResponse (id, resp, token); + return true; + } + + var start = SourceLocation.Parse (args? ["start"] as JObject); + //FIXME support variant where restrictToFunction=true and end is omitted + var end = SourceLocation.Parse (args? ["end"] as JObject); + if (start != null && end != null && await GetPossibleBreakpoints (id, start, end, token)) + return true; + + SendResponse (id, resp, token); + return true; + } + + case "Debugger.setBreakpoint": { + break; + } + + case "Debugger.setBreakpointByUrl": { + var resp = await SendCommand (id, method, args, token); + if (!resp.IsOk) { + SendResponse (id, resp, token); + return true; + } + + var bpid = resp.Value["breakpointId"]?.ToString (); + var locations = resp.Value["locations"]?.Values<object>(); + var request = BreakpointRequest.Parse (bpid, args); + + // is the store done loading? + var loaded = context.Source.Task.IsCompleted; + if (!loaded) { + // Send and empty response immediately if not + // and register the breakpoint for resolution + context.BreakpointRequests [bpid] = request; + SendResponse (id, resp, token); + } + + if (await IsRuntimeAlreadyReadyAlready (id, token)) { + var store = await RuntimeReady (id, token); + + Log ("verbose", $"BP req {args}"); + await SetBreakpoint (id, store, request, !loaded, token); + } + + if (loaded) { + // we were already loaded so we should send a response + // with the locations included and register the request + context.BreakpointRequests [bpid] = request; + var result = Result.OkFromObject (request.AsSetBreakpointByUrlResponse (locations)); + SendResponse (id, result, token); + + } + return true; + } + + case "Debugger.removeBreakpoint": { + await RemoveBreakpoint (id, args, token); + break; + } + + case "Debugger.resume": { + await OnResume (id, token); + break; + } + + case "Debugger.stepInto": { + return await Step (id, StepKind.Into, token); + } + + case "Debugger.stepOut": { + return await Step (id, StepKind.Out, token); + } + + case "Debugger.stepOver": { + return await Step (id, StepKind.Over, token); + } + + case "Debugger.evaluateOnCallFrame": { + if (!DotnetObjectId.TryParse (args? ["callFrameId"], out var objectId)) + return false; + + switch (objectId.Scheme) { + case "scope": + return await OnEvaluateOnCallFrame (id, + int.Parse (objectId.Value), + args? ["expression"]?.Value<string> (), token); + default: + return false; + } + } + + case "Runtime.getProperties": { + if (!DotnetObjectId.TryParse (args? ["objectId"], out var objectId)) + break; + + var result = await RuntimeGetProperties (id, objectId, args, token); + SendResponse (id, result, token); + return true; + } + + case "Runtime.releaseObject": { + if (!(DotnetObjectId.TryParse (args ["objectId"], out var objectId) && objectId.Scheme == "cfo_res")) + break; + + await SendMonoCommand (id, MonoCommands.ReleaseObject (objectId), token); + SendResponse (id, Result.OkFromObject (new{}), token); + return true; + } + + // Protocol extensions + case "Dotnet-test.setBreakpointByMethod": { + Console.WriteLine ("set-breakpoint-by-method: " + id + " " + args); + + var store = await RuntimeReady (id, token); + string aname = args ["assemblyName"]?.Value<string> (); + string typeName = args ["typeName"]?.Value<string> (); + string methodName = args ["methodName"]?.Value<string> (); + if (aname == null || typeName == null || methodName == null) { + SendResponse (id, Result.Err ("Invalid protocol message '" + args + "'."), token); + return true; + } + + // GetAssemblyByName seems to work on file names + var assembly = store.GetAssemblyByName (aname); + if (assembly == null) + assembly = store.GetAssemblyByName (aname + ".exe"); + if (assembly == null) + assembly = store.GetAssemblyByName (aname + ".dll"); + if (assembly == null) { + SendResponse (id, Result.Err ("Assembly '" + aname + "' not found."), token); + return true; + } + + var type = assembly.GetTypeByName (typeName); + if (type == null) { + SendResponse (id, Result.Err ($"Type '{typeName}' not found."), token); + return true; + } + + var methodInfo = type.Methods.FirstOrDefault (m => m.Name == methodName); + if (methodInfo == null) { + SendResponse (id, Result.Err ($"Method '{typeName}:{methodName}' not found."), token); + return true; + } + + bpIdGenerator ++; + string bpid = "by-method-" + bpIdGenerator.ToString (); + var request = new BreakpointRequest (bpid, methodInfo); + context.BreakpointRequests[bpid] = request; + + var loc = methodInfo.StartLocation; + var bp = await SetMonoBreakpoint (id, bpid, loc, token); + if (bp.State != BreakpointState.Active) { + // FIXME: + throw new NotImplementedException (); + } + + var resolvedLocation = new { + breakpointId = bpid, + location = loc.AsLocation () + }; + + SendEvent (id, "Debugger.breakpointResolved", JObject.FromObject (resolvedLocation), token); + + SendResponse (id, Result.OkFromObject (new { + result = new { breakpointId = bpid, locations = new object [] { loc.AsLocation () }} + }), token); + + return true; + } + case "Runtime.callFunctionOn": { + if (!DotnetObjectId.TryParse (args ["objectId"], out var objectId)) + return false; + + var silent = args ["silent"]?.Value<bool> () ?? false; + if (objectId.Scheme == "scope") { + var fail = silent ? Result.OkFromObject (new { result = new { } }) : Result.Exception (new ArgumentException ($"Runtime.callFunctionOn not supported with scope ({objectId}).")); + + SendResponse (id, fail, token); + return true; + } + + var returnByValue = args ["returnByValue"]?.Value<bool> () ?? false; + var res = await SendMonoCommand (id, MonoCommands.CallFunctionOn (args), token); + + if (!returnByValue && + DotnetObjectId.TryParse (res.Value?["result"]?["value"]?["objectId"], out var resultObjectId) && + resultObjectId.Scheme == "cfo_res") + res = Result.OkFromObject (new { result = res.Value ["result"]["value"] }); + + if (res.IsErr && silent) + res = Result.OkFromObject (new { result = new { } }); + + SendResponse (id, res, token); + return true; + } + } + + return false; + } + + async Task<Result> RuntimeGetProperties (MessageId id, DotnetObjectId objectId, JToken args, CancellationToken token) + { + if (objectId.Scheme == "scope") + return await GetScopeProperties (id, int.Parse (objectId.Value), token); + + var res = await SendMonoCommand (id, MonoCommands.GetDetails (objectId, args), token); + if (res.IsErr) + return res; + + if (objectId.Scheme == "cfo_res") { + // Runtime.callFunctionOn result object + var value_json_str = res.Value ["result"]?["value"]?["__value_as_json_string__"]?.Value<string> (); + if (value_json_str != null) { + res = Result.OkFromObject (new { + result = JArray.Parse (value_json_str.Replace (@"\""", "\"")) + }); + } else { + res = Result.OkFromObject (new { result = new {} }); + } + } else { + res = Result.Ok (JObject.FromObject (new { result = res.Value ["result"] ["value"] })); + } + + return res; + } + + //static int frame_id=0; + async Task<bool> OnBreakpointHit (SessionId sessionId, JObject args, CancellationToken token) + { + //FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime + var res = await SendMonoCommand (sessionId, MonoCommands.GetCallStack(), token); + var orig_callframes = args? ["callFrames"]?.Values<JObject> (); + var context = GetContext (sessionId); + + if (res.IsErr) { + //Give up and send the original call stack + return false; + } + + //step one, figure out where did we hit + var res_value = res.Value? ["result"]? ["value"]; + if (res_value == null || res_value is JValue) { + //Give up and send the original call stack + return false; + } + + Log ("verbose", $"call stack (err is {res.Error} value is:\n{res.Value}"); + var bp_id = res_value? ["breakpoint_id"]?.Value<int> (); + Log ("verbose", $"We just hit bp {bp_id}"); + if (!bp_id.HasValue) { + //Give up and send the original call stack + return false; + } + + var bp = context.BreakpointRequests.Values.SelectMany (v => v.Locations).FirstOrDefault (b => b.RemoteId == bp_id.Value); + + var callFrames = new List<object> (); + foreach (var frame in orig_callframes) { + var function_name = frame ["functionName"]?.Value<string> (); + var url = frame ["url"]?.Value<string> (); + if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name) { + var frames = new List<Frame> (); + int frame_id = 0; + var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values<JObject> (); + + foreach (var mono_frame in the_mono_frames) { + ++frame_id; + var il_pos = mono_frame ["il_pos"].Value<int> (); + var method_token = mono_frame ["method_token"].Value<uint> (); + var assembly_name = mono_frame ["assembly_name"].Value<string> (); + + // This can be different than `method.Name`, like in case of generic methods + var method_name = mono_frame ["method_name"]?.Value<string> (); + + var store = await LoadStore (sessionId, token); + var asm = store.GetAssemblyByName (assembly_name); + if (asm == null) { + Log ("info",$"Unable to find assembly: {assembly_name}"); + continue; + } + + var method = asm.GetMethodByToken (method_token); + + if (method == null) { + Log ("info", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}"); + continue; + } + + var location = method?.GetLocationByIl (il_pos); + + // When hitting a breakpoint on the "IncrementCount" method in the standard + // Blazor project template, one of the stack frames is inside mscorlib.dll + // and we get location==null for it. It will trigger a NullReferenceException + // if we don't skip over that stack frame. + if (location == null) { + continue; + } + + Log ("info", $"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}"); + Log ("info", $"\tmethod {method_name} location: {location}"); + frames.Add (new Frame (method, location, frame_id-1)); + + callFrames.Add (new { + functionName = method_name, + callFrameId = $"dotnet:scope:{frame_id-1}", + functionLocation = method.StartLocation.AsLocation (), + + location = location.AsLocation (), + + url = store.ToUrl (location), + + scopeChain = new [] { + new { + type = "local", + @object = new { + @type = "object", + className = "Object", + description = "Object", + objectId = $"dotnet:scope:{frame_id-1}", + }, + name = method_name, + startLocation = method.StartLocation.AsLocation (), + endLocation = method.EndLocation.AsLocation (), + }} + }); + + context.CallStack = frames; + + } + } else if (!(function_name.StartsWith ("wasm-function", StringComparison.Ordinal) + || url.StartsWith ("wasm://wasm/", StringComparison.Ordinal))) { + callFrames.Add (frame); + } + } + + var bp_list = new string [bp == null ? 0 : 1]; + if (bp != null) + bp_list [0] = bp.StackId; + + var o = JObject.FromObject (new { + callFrames, + reason = "other", //other means breakpoint + hitBreakpoints = bp_list, + }); + + SendEvent (sessionId, "Debugger.paused", o, token); + return true; + } + + async Task OnDefaultContext (SessionId sessionId, ExecutionContext context, CancellationToken token) + { + Log ("verbose", "Default context created, clearing state and sending events"); + if (UpdateContext (sessionId, context, out var previousContext)) { + foreach (var kvp in previousContext.BreakpointRequests) { + context.BreakpointRequests[kvp.Key] = kvp.Value.Clone(); + } + } + + if (await IsRuntimeAlreadyReadyAlready (sessionId, token)) + await RuntimeReady (sessionId, token); + } + + async Task OnResume (MessageId msd_id, CancellationToken token) + { + //discard managed frames + GetContext (msd_id).ClearState (); + await Task.CompletedTask; + } + + async Task<bool> Step (MessageId msg_id, StepKind kind, CancellationToken token) + { + var context = GetContext (msg_id); + if (context.CallStack == null) + return false; + + if (context.CallStack.Count <= 1 && kind == StepKind.Out) + return false; + + var res = await SendMonoCommand (msg_id, MonoCommands.StartSingleStepping (kind), token); + + var ret_code = res.Value? ["result"]? ["value"]?.Value<int> (); + + if (ret_code.HasValue && ret_code.Value == 0) { + context.ClearState (); + await SendCommand (msg_id, "Debugger.stepOut", new JObject (), token); + return false; + } + + SendResponse (msg_id, Result.Ok (new JObject ()), token); + + context.ClearState (); + + await SendCommand (msg_id, "Debugger.resume", new JObject (), token); + return true; + } + + internal bool TryFindVariableValueInCache(ExecutionContext ctx, string expression, bool only_search_on_this, out JToken obj) + { + if (ctx.LocalsCache.TryGetValue (expression, out obj)) { + if (only_search_on_this && obj["fromThis"] == null) + return false; + return true; + } + return false; + } + + internal async Task<JToken> TryGetVariableValue (MessageId msg_id, int scope_id, string expression, bool only_search_on_this, CancellationToken token) + { + JToken thisValue = null; + var context = GetContext (msg_id); + if (context.CallStack == null) + return null; + + if (TryFindVariableValueInCache(context, expression, only_search_on_this, out JToken obj)) + return obj; + + var scope = context.CallStack.FirstOrDefault (s => s.Id == scope_id); + var live_vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset); + //get_this + var res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, live_vars.Select (lv => lv.Index).ToArray ()), token); + + var scope_values = res.Value? ["result"]? ["value"]?.Values<JObject> ()?.ToArray (); + thisValue = scope_values?.FirstOrDefault (v => v ["name"]?.Value<string> () == "this"); + + if (!only_search_on_this) { + if (thisValue != null && expression == "this") + return thisValue; + + var value = scope_values.SingleOrDefault (sv => sv ["name"]?.Value<string> () == expression); + if (value != null) + return value; + } + + //search in scope + if (thisValue != null) { + if (!DotnetObjectId.TryParse (thisValue ["value"] ["objectId"], out var objectId)) + return null; + + res = await SendMonoCommand (msg_id, MonoCommands.GetDetails (objectId), token); + scope_values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray (); + var foundValue = scope_values.FirstOrDefault (v => v ["name"].Value<string> () == expression); + if (foundValue != null) { + foundValue["fromThis"] = true; + context.LocalsCache[foundValue ["name"].Value<string> ()] = foundValue; + return foundValue; + } + } + return null; + } + + async Task<bool> OnEvaluateOnCallFrame (MessageId msg_id, int scope_id, string expression, CancellationToken token) + { + try { + var context = GetContext (msg_id); + if (context.CallStack == null) + return false; + + var varValue = await TryGetVariableValue (msg_id, scope_id, expression, false, token); + + if (varValue != null) { + SendResponse (msg_id, Result.OkFromObject (new { + result = varValue ["value"] + }), token); + return true; + } + + string retValue = await EvaluateExpression.CompileAndRunTheExpression (this, msg_id, scope_id, expression, token); + SendResponse (msg_id, Result.OkFromObject (new { + result = new { + value = retValue + } + }), token); + return true; + } catch (Exception e) { + logger.LogDebug (e, $"Error in EvaluateOnCallFrame for expression '{expression}."); + } + return false; + } + + async Task<Result> GetScopeProperties (MessageId msg_id, int scope_id, CancellationToken token) + { + try { + var ctx = GetContext (msg_id); + var scope = ctx.CallStack.FirstOrDefault (s => s.Id == scope_id); + if (scope == null) + return Result.Err (JObject.FromObject (new { message = $"Could not find scope with id #{scope_id}" })); + + var vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset); + + var var_ids = vars.Select (v => v.Index).ToArray (); + var res = await SendMonoCommand (msg_id, MonoCommands.GetScopeVariables (scope.Id, var_ids), token); + + //if we fail we just buble that to the IDE (and let it panic over it) + if (res.IsErr) + return res; + + var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray (); + + if(values == null) + return Result.OkFromObject (new { result = Array.Empty<object> () }); + + var var_list = new List<object> (); + int i = 0; + for (; i < vars.Length && i < values.Length; i ++) { + // For async methods, we get locals with names, unlike non-async methods + // and the order may not match the var_ids, so, use the names that they + // come with + if (values [i]["name"] != null) + continue; + + ctx.LocalsCache[vars [i].Name] = values [i]; + var_list.Add (new { name = vars [i].Name, value = values [i]["value"] }); + } + for (; i < values.Length; i ++) { + ctx.LocalsCache[values [i]["name"].ToString()] = values [i]; + var_list.Add (values [i]); + } + + return Result.OkFromObject (new { result = var_list }); + } catch (Exception exception) { + Log ("verbose", $"Error resolving scope properties {exception.Message}"); + return Result.Exception (exception); + } + } + + async Task<Breakpoint> SetMonoBreakpoint (SessionId sessionId, string reqId, SourceLocation location, CancellationToken token) + { + var bp = new Breakpoint (reqId, location, BreakpointState.Pending); + var asm_name = bp.Location.CliLocation.Method.Assembly.Name; + var method_token = bp.Location.CliLocation.Method.Token; + var il_offset = bp.Location.CliLocation.Offset; + + var res = await SendMonoCommand (sessionId, MonoCommands.SetBreakpoint (asm_name, method_token, il_offset), token); + var ret_code = res.Value? ["result"]? ["value"]?.Value<int> (); + + if (ret_code.HasValue) { + bp.RemoteId = ret_code.Value; + bp.State = BreakpointState.Active; + //Log ("verbose", $"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}"); + } + + return bp; + } + + async Task<DebugStore> LoadStore (SessionId sessionId, CancellationToken token) + { + var context = GetContext (sessionId); + + if (Interlocked.CompareExchange (ref context.store, new DebugStore (logger), null) != null) + return await context.Source.Task; + + try { + var loaded_pdbs = await SendMonoCommand (sessionId, MonoCommands.GetLoadedFiles(), token); + var the_value = loaded_pdbs.Value? ["result"]? ["value"]; + var the_pdbs = the_value?.ToObject<string[]> (); + + await foreach (var source in context.store.Load(sessionId, the_pdbs, token).WithCancellation (token)) { + var scriptSource = JObject.FromObject (source.ToScriptSource (context.Id, context.AuxData)); + Log ("verbose", $"\tsending {source.Url} {context.Id} {sessionId.sessionId}"); + + SendEvent (sessionId, "Debugger.scriptParsed", scriptSource, token); + + foreach (var req in context.BreakpointRequests.Values) { + if (req.TryResolve (source)) { + await SetBreakpoint (sessionId, context.store, req, true, token); + } + } + } + } catch (Exception e) { + context.Source.SetException (e); + } + + if (!context.Source.Task.IsCompleted) + context.Source.SetResult (context.store); + return context.store; + } + + async Task<DebugStore> RuntimeReady (SessionId sessionId, CancellationToken token) + { + var context = GetContext (sessionId); + if (Interlocked.CompareExchange (ref context.ready, new TaskCompletionSource<DebugStore> (), null) != null) + return await context.ready.Task; + + var clear_result = await SendMonoCommand (sessionId, MonoCommands.ClearAllBreakpoints (), token); + if (clear_result.IsErr) { + Log ("verbose", $"Failed to clear breakpoints due to {clear_result}"); + } + + var store = await LoadStore (sessionId, token); + + context.ready.SetResult (store); + SendEvent (sessionId, "Mono.runtimeReady", new JObject (), token); + return store; + } + + async Task RemoveBreakpoint(MessageId msg_id, JObject args, CancellationToken token) { + var bpid = args? ["breakpointId"]?.Value<string> (); + + var context = GetContext (msg_id); + if (!context.BreakpointRequests.TryGetValue (bpid, out var breakpointRequest)) + return; + + foreach (var bp in breakpointRequest.Locations) { + var res = await SendMonoCommand (msg_id, MonoCommands.RemoveBreakpoint (bp.RemoteId), token); + var ret_code = res.Value? ["result"]? ["value"]?.Value<int> (); + + if (ret_code.HasValue) { + bp.RemoteId = -1; + bp.State = BreakpointState.Disabled; + } + } + breakpointRequest.Locations.Clear (); + } + + async Task SetBreakpoint (SessionId sessionId, DebugStore store, BreakpointRequest req, bool sendResolvedEvent, CancellationToken token) + { + var context = GetContext (sessionId); + if (req.Locations.Any ()) { + Log ("debug", $"locations already loaded for {req.Id}"); + return; + } + + var comparer = new SourceLocation.LocationComparer (); + // if column is specified the frontend wants the exact matches + // and will clear the bp if it isn't close enoug + var locations = store.FindBreakpointLocations (req) + .Distinct (comparer) + .Where (l => l.Line == req.Line && (req.Column == 0 || l.Column == req.Column)) + .OrderBy (l => l.Column) + .GroupBy (l => l.Id); + + logger.LogDebug ("BP request for '{req}' runtime ready {context.RuntimeReady}", req, GetContext (sessionId).IsRuntimeReady); + + var breakpoints = new List<Breakpoint> (); + + foreach (var sourceId in locations) { + var loc = sourceId.First (); + var bp = await SetMonoBreakpoint (sessionId, req.Id, loc, token); + + // If we didn't successfully enable the breakpoint + // don't add it to the list of locations for this id + if (bp.State != BreakpointState.Active) + continue; + + breakpoints.Add (bp); + + var resolvedLocation = new { + breakpointId = req.Id, + location = loc.AsLocation () + }; + + if (sendResolvedEvent) + SendEvent (sessionId, "Debugger.breakpointResolved", JObject.FromObject (resolvedLocation), token); + } + + req.Locations.AddRange (breakpoints); + return; + } + + async Task<bool> GetPossibleBreakpoints (MessageId msg, SourceLocation start, SourceLocation end, CancellationToken token) + { + var bps = (await RuntimeReady (msg, token)).FindPossibleBreakpoints (start, end); + + if (bps == null) + return false; + + var response = new { locations = bps.Select (b => b.AsLocation ()) }; + + SendResponse (msg, Result.OkFromObject (response), token); + return true; + } + + void OnCompileDotnetScript (MessageId msg_id, CancellationToken token) + { + SendResponse (msg_id, Result.OkFromObject (new { }), token); + } + + async Task<bool> OnGetScriptSource (MessageId msg_id, string script_id, CancellationToken token) + { + if (!SourceId.TryParse (script_id, out var id)) + return false; + + var src_file = (await LoadStore (msg_id, token)).GetFileById (id); + + try { + var uri = new Uri (src_file.Url); + string source = $"// Unable to find document {src_file.SourceUri}"; + + using (var data = await src_file.GetSourceAsync (checkHash: false, token: token)) { + if (data.Length == 0) + return false; + + using (var reader = new StreamReader (data)) + source = await reader.ReadToEndAsync (); + } + SendResponse (msg_id, Result.OkFromObject (new { scriptSource = source }), token); + } catch (Exception e) { + var o = new { + scriptSource = $"// Unable to read document ({e.Message})\n" + + $"Local path: {src_file?.SourceUri}\n" + + $"SourceLink path: {src_file?.SourceLinkUri}\n" + }; + + SendResponse (msg_id, Result.OkFromObject (o), token); + } + return true; + } + + async Task DeleteWebDriver (SessionId sessionId, CancellationToken token) + { + // see https://github.com/mono/mono/issues/19549 for background + if (hideWebDriver && sessions.Add (sessionId)) { + var res = await SendCommand (sessionId, + "Page.addScriptToEvaluateOnNewDocument", + JObject.FromObject (new { source = "delete navigator.constructor.prototype.webdriver"}), + token); + + if (sessionId != SessionId.Null && !res.IsOk) + sessions.Remove (sessionId); + } + } + } +} diff --git a/src/Components/WebAssembly/DebugProxy/src/Program.cs b/src/Components/WebAssembly/DebugProxy/src/Program.cs new file mode 100644 index 0000000000000000000000000000000000000000..ce67276e6e0481c442fc14f1ed13fd4330b22eca --- /dev/null +++ b/src/Components/WebAssembly/DebugProxy/src/Program.cs @@ -0,0 +1,71 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.Hosting; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.CommandLineUtils; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.AspNetCore.Components.WebAssembly.DebugProxy +{ + public class Program + { + static int Main(string[] args) + { + var app = new CommandLineApplication(throwOnUnexpectedArg: false) + { + Name = "webassembly-debugproxy" + }; + app.HelpOption("-?|-h|--help"); + + var browserHostOption = new CommandOption("-b|--browser-host", CommandOptionType.SingleValue) + { + Description = "Host on which the browser is listening for debug connections. Example: http://localhost:9300" + }; + + var ownerPidOption = new CommandOption("-op|--owner-pid", CommandOptionType.SingleValue) + { + Description = "ID of the owner process. The debug proxy will shut down if this process exits." + }; + + app.Options.Add(browserHostOption); + app.Options.Add(ownerPidOption); + + app.OnExecute(() => + { + var browserHost = browserHostOption.HasValue() ? browserHostOption.Value(): "http://127.0.0.1:9222"; + var host = DebugProxyHost.CreateDefaultBuilder(args, browserHost).Build(); + + if (ownerPidOption.HasValue()) + { + var ownerProcess = Process.GetProcessById(int.Parse(ownerPidOption.Value())); + ownerProcess.EnableRaisingEvents = true; + ownerProcess.Exited += async (sender, eventArgs) => + { + Console.WriteLine("Exiting because parent process has exited"); + await host.StopAsync(); + }; + } + + host.Run(); + + return 0; + }); + + try + { + return app.Execute(args); + } + catch (CommandParsingException cex) + { + app.Error.WriteLine(cex.Message); + app.ShowHelp(); + return 1; + } + } + } +} diff --git a/src/Components/WebAssembly/DebugProxy/src/Startup.cs b/src/Components/WebAssembly/DebugProxy/src/Startup.cs new file mode 100644 index 0000000000000000000000000000000000000000..9a80b2a2527a863e12615dab2c9463f3db439b1e --- /dev/null +++ b/src/Components/WebAssembly/DebugProxy/src/Startup.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using WebAssembly.Net.Debugging; + +namespace Microsoft.AspNetCore.Components.WebAssembly.DebugProxy +{ + public class Startup + { + public void Configure(IApplicationBuilder app, DebugProxyOptions debugProxyOptions) + { + app.UseDeveloperExceptionPage(); + app.UseWebSockets(); + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + // At the homepage, we check whether we can uniquely identify the target tab + // - If yes, we redirect directly to the debug tools, proxying to that tab + // - If no, we present a list of available tabs for the user to pick from + endpoints.MapGet("/", new TargetPickerUi(debugProxyOptions).Display); + + // At this URL, we wire up the actual WebAssembly proxy + endpoints.MapGet("/ws-proxy", async (context) => + { + if (!context.WebSockets.IsWebSocketRequest) + { + context.Response.StatusCode = (int)HttpStatusCode.BadRequest; + return; + } + + var loggerFactory = context.RequestServices.GetRequiredService<ILoggerFactory>(); + var browserUri = new Uri(context.Request.Query["browser"]); + var ideSocket = await context.WebSockets.AcceptWebSocketAsync(); + await new MonoProxy(loggerFactory).Run(browserUri, ideSocket); + }); + }); + } + } +} diff --git a/src/Components/WebAssembly/DebugProxy/src/TargetPickerUi.cs b/src/Components/WebAssembly/DebugProxy/src/TargetPickerUi.cs new file mode 100644 index 0000000000000000000000000000000000000000..249e2e5f379fa36bf6c8d57eb798bb8e8fcc026c --- /dev/null +++ b/src/Components/WebAssembly/DebugProxy/src/TargetPickerUi.cs @@ -0,0 +1,226 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Runtime.InteropServices; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Components.WebAssembly.DebugProxy +{ + public class TargetPickerUi + { + private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + IgnoreNullValues = true + }; + + private readonly DebugProxyOptions _options; + + public TargetPickerUi(DebugProxyOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + } + + public async Task Display(HttpContext context) + { + context.Response.ContentType = "text/html"; + + var request = context.Request; + var targetApplicationUrl = request.Query["url"]; + + var debuggerTabsListUrl = $"{_options.BrowserHost}/json"; + IEnumerable<BrowserTab> availableTabs; + + try + { + availableTabs = await GetOpenedBrowserTabs(); + } + catch (Exception ex) + { + await context.Response.WriteAsync($@" +<h1>Unable to find debuggable browser tab</h1> +<p> + Could not get a list of browser tabs from <code>{debuggerTabsListUrl}</code>. + Ensure your browser is running with debugging enabled. +</p> +<h2>Resolution</h2> +<p> + <h4>If you are using Google Chrome for your development, follow these instructions:</h4> + {GetLaunchChromeInstructions(targetApplicationUrl)} +</p> +<p> + <h4>If you are using Microsoft Edge (80+) for your development, follow these instructions:</h4> + {GetLaunchEdgeInstructions(targetApplicationUrl)} +</p> +<strong>This should launch a new browser window with debugging enabled..</p> +<h2>Underlying exception:</h2> +<pre>{ex}</pre> + "); + + return; + } + + var matchingTabs = string.IsNullOrEmpty(targetApplicationUrl) + ? availableTabs.ToList() + : availableTabs.Where(t => t.Url.Equals(targetApplicationUrl, StringComparison.Ordinal)).ToList(); + + if (matchingTabs.Count == 1) + { + // We know uniquely which tab to debug, so just redirect + var devToolsUrlWithProxy = GetDevToolsUrlWithProxy(request, matchingTabs.Single()); + context.Response.Redirect(devToolsUrlWithProxy); + } + else if (matchingTabs.Count == 0) + { + await context.Response.WriteAsync("<h1>No inspectable pages found</h1>"); + + var suffix = string.IsNullOrEmpty(targetApplicationUrl) + ? string.Empty + : $" matching the URL {WebUtility.HtmlEncode(targetApplicationUrl)}"; + await context.Response.WriteAsync($"<p>The list of targets returned by {WebUtility.HtmlEncode(debuggerTabsListUrl)} contains no entries{suffix}.</p>"); + await context.Response.WriteAsync("<p>Make sure your browser is displaying the target application.</p>"); + } + else + { + await context.Response.WriteAsync("<h1>Inspectable pages</h1>"); + await context.Response.WriteAsync(@" + <style type='text/css'> + body { + font-family: Helvetica, Arial, sans-serif; + margin: 2rem 3rem; + } + + .inspectable-page { + display: block; + background-color: #eee; + padding: 1rem 1.2rem; + margin-bottom: 1rem; + border-radius: 0.5rem; + text-decoration: none; + color: #888; + } + + .inspectable-page:hover { + background-color: #fed; + } + + .inspectable-page h3 { + margin-top: 0px; + margin-bottom: 0.3rem; + color: black; + } + </style> + "); + + foreach (var tab in matchingTabs) + { + var devToolsUrlWithProxy = GetDevToolsUrlWithProxy(request, tab); + await context.Response.WriteAsync( + $"<a class='inspectable-page' href='{WebUtility.HtmlEncode(devToolsUrlWithProxy)}'>" + + $"<h3>{WebUtility.HtmlEncode(tab.Title)}</h3>{WebUtility.HtmlEncode(tab.Url)}" + + $"</a>"); + } + } + } + + private string GetDevToolsUrlWithProxy(HttpRequest request, BrowserTab tabToDebug) + { + var underlyingV8Endpoint = tabToDebug.WebSocketDebuggerUrl; + var proxyEndpoint = GetProxyEndpoint(request, underlyingV8Endpoint); + var devToolsUrlAbsolute = new Uri(_options.BrowserHost + tabToDebug.DevtoolsFrontendUrl); + var devToolsUrlWithProxy = $"{devToolsUrlAbsolute.Scheme}://{devToolsUrlAbsolute.Authority}{devToolsUrlAbsolute.AbsolutePath}?{proxyEndpoint.Scheme}={proxyEndpoint.Authority}{proxyEndpoint.PathAndQuery}"; + return devToolsUrlWithProxy; + } + + private string GetLaunchChromeInstructions(string targetApplicationUrl) + { + var profilePath = Path.Combine(Path.GetTempPath(), "blazor-chrome-debug"); + var debuggerPort = new Uri(_options.BrowserHost).Port; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return $@"<p>Press Win+R and enter the following:</p> + <p><strong><code>chrome --remote-debugging-port={debuggerPort} --user-data-dir=""{profilePath}"" {targetApplicationUrl}</code></strong></p>"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return $@"<p>In a terminal window execute the following:</p> + <p><strong><code>google-chrome --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {targetApplicationUrl}</code></strong></p>"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return $@"<p>Execute the following:</p> + <p><strong><code>open /Applications/Google\ Chrome.app --args --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {targetApplicationUrl}</code></strong></p>"; + } + else + { + throw new InvalidOperationException("Unknown OS platform"); + } + } + + private string GetLaunchEdgeInstructions(string targetApplicationUrl) + { + var profilePath = Path.Combine(Path.GetTempPath(), "blazor-edge-debug"); + var debuggerPort = new Uri(_options.BrowserHost).Port; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return $@"<p>Press Win+R and enter the following:</p> + <p><strong><code>msedge --remote-debugging-port={debuggerPort} --user-data-dir=""{profilePath}"" --no-first-run {targetApplicationUrl}</code></strong></p>"; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return $@"<p>In a terminal window execute the following:</p> + <p><strong><code>open /Applications/Microsoft\ Edge\ Dev.app --args --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {targetApplicationUrl}</code></strong></p>"; + } + else + { + return $@"<p>Edge is not current supported on your platform</p>"; + } + } + + private static Uri GetProxyEndpoint(HttpRequest incomingRequest, string browserEndpoint) + { + var builder = new UriBuilder( + schemeName: incomingRequest.IsHttps ? "wss" : "ws", + hostName: incomingRequest.Host.Host) + { + Path = $"{incomingRequest.PathBase}/ws-proxy", + Query = $"browser={WebUtility.UrlEncode(browserEndpoint)}" + }; + + if (incomingRequest.Host.Port.HasValue) + { + builder.Port = incomingRequest.Host.Port.Value; + } + + return builder.Uri; + } + + private async Task<IEnumerable<BrowserTab>> GetOpenedBrowserTabs() + { + using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }; + var jsonResponse = await httpClient.GetStringAsync($"{_options.BrowserHost}/json"); + return JsonSerializer.Deserialize<BrowserTab[]>(jsonResponse, JsonOptions); + } + + class BrowserTab + { + public string Id { get; set; } + public string Type { get; set; } + public string Url { get; set; } + public string Title { get; set; } + public string DevtoolsFrontendUrl { get; set; } + public string WebSocketDebuggerUrl { get; set; } + } + } +} diff --git a/src/Components/Blazor/DevServer/src/Commands/ServeCommand.cs b/src/Components/WebAssembly/DevServer/src/Commands/ServeCommand.cs similarity index 73% rename from src/Components/Blazor/DevServer/src/Commands/ServeCommand.cs rename to src/Components/WebAssembly/DevServer/src/Commands/ServeCommand.cs index eb7a347051be8538bb259b49bcc40d1aca05fe98..ad6d1ce00cfc57768e235834d51f9f9ae3c8d480 100644 --- a/src/Components/Blazor/DevServer/src/Commands/ServeCommand.cs +++ b/src/Components/WebAssembly/DevServer/src/Commands/ServeCommand.cs @@ -4,18 +4,19 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Hosting; +using DevServerProgram = Microsoft.AspNetCore.Components.WebAssembly.DevServer.Server.Program; -namespace Microsoft.AspNetCore.Blazor.DevServer.Commands +namespace Microsoft.AspNetCore.Components.Web.DevServer.Commands { internal class ServeCommand : CommandLineApplication { public ServeCommand(CommandLineApplication parent) // We pass arbitrary arguments through to the ASP.NET Core configuration - : base(throwOnUnexpectedArg: false) + : base(throwOnUnexpectedArg: false) { Parent = parent; - + Name = "serve"; Description = "Serve requests to a Blazor application"; @@ -26,7 +27,7 @@ namespace Microsoft.AspNetCore.Blazor.DevServer.Commands private int Execute() { - Server.Program.BuildWebHost(RemainingArguments.ToArray()).Run(); + DevServerProgram.BuildWebHost(RemainingArguments.ToArray()).Run(); return 0; } } diff --git a/src/Components/Blazor/DevServer/src/Microsoft.AspNetCore.Blazor.DevServer.csproj b/src/Components/WebAssembly/DevServer/src/Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj similarity index 66% rename from src/Components/Blazor/DevServer/src/Microsoft.AspNetCore.Blazor.DevServer.csproj rename to src/Components/WebAssembly/DevServer/src/Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj index 4ccf78104b9cefbc40543804ee9073d032051dd4..6e8f84a388e41fc2896346f21d6cd41b0d188f2b 100644 --- a/src/Components/Blazor/DevServer/src/Microsoft.AspNetCore.Blazor.DevServer.csproj +++ b/src/Components/WebAssembly/DevServer/src/Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj @@ -4,34 +4,33 @@ <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> <OutputType>Exe</OutputType> <AssemblyName>blazor-devserver</AssemblyName> - <PackageId>Microsoft.AspNetCore.Blazor.DevServer</PackageId> - <IsShippingPackage>false</IsShippingPackage> + <PackageId>Microsoft.AspNetCore.Components.WebAssembly.DevServer</PackageId> + <IsShippingPackage>true</IsShippingPackage> <HasReferenceAssembly>false</HasReferenceAssembly> - <StartupObject>Microsoft.AspNetCore.Blazor.DevServer.Program</StartupObject> + <StartupObject>Microsoft.AspNetCore.Components.WebAssembly.DevServer.Program</StartupObject> <Description>Development server for use when building Blazor applications.</Description> <!-- Set this to false because assemblies should not reference this assembly directly, (except for tests, of course). --> <IsProjectReferenceProvider>false</IsProjectReferenceProvider> - - <!-- This is so that we add the FrameworkReference to Microsoft.AspNetCore.App --> - <UseLatestAspNetCoreReference>true</UseLatestAspNetCoreReference> </PropertyGroup> <ItemGroup> - <Reference Include="Microsoft.AspNetCore.Blazor.Server" /> + <Reference Include="Microsoft.AspNetCore" /> + <Reference Include="Microsoft.AspNetCore.Diagnostics" /> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" /> <Reference Include="Microsoft.Extensions.CommandLineUtils.Sources" /> + <Reference Include="Microsoft.Extensions.Hosting" /> </ItemGroup> <!-- Pack settings --> <PropertyGroup> <GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);publish</GenerateNuspecDependsOn> <NoPackageAnalysis>true</NoPackageAnalysis> - <NuspecFile>Microsoft.AspNetCore.Blazor.DevServer.nuspec</NuspecFile> + <NuspecFile>Microsoft.AspNetCore.Components.WebAssembly.DevServer.nuspec</NuspecFile> </PropertyGroup> <ItemGroup> <NuspecProperty Include="publishDir=$(PublishDir)" /> <NuspecProperty Include="componentsrootdir=..\..\..\" /> - <NuspecProperty Include="blazorversion=$(PackageVersion)" /> <NuspecProperty Include="PackageThirdPartyNoticesFile=$(PackageThirdPartyNoticesFile)" /> </ItemGroup> diff --git a/src/Components/Blazor/DevServer/src/Microsoft.AspNetCore.Blazor.DevServer.nuspec b/src/Components/WebAssembly/DevServer/src/Microsoft.AspNetCore.Components.WebAssembly.DevServer.nuspec similarity index 100% rename from src/Components/Blazor/DevServer/src/Microsoft.AspNetCore.Blazor.DevServer.nuspec rename to src/Components/WebAssembly/DevServer/src/Microsoft.AspNetCore.Components.WebAssembly.DevServer.nuspec diff --git a/src/Components/Blazor/DevServer/src/Program.cs b/src/Components/WebAssembly/DevServer/src/Program.cs similarity index 88% rename from src/Components/Blazor/DevServer/src/Program.cs rename to src/Components/WebAssembly/DevServer/src/Program.cs index 30eb3927f0ac901c62f660a5a059c2bc6628a599..29763955f5a6dd5933fe9d8f06acf2a874378300 100644 --- a/src/Components/Blazor/DevServer/src/Program.cs +++ b/src/Components/WebAssembly/DevServer/src/Program.cs @@ -1,10 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.AspNetCore.Blazor.DevServer.Commands; +using Microsoft.AspNetCore.Components.Web.DevServer.Commands; using Microsoft.Extensions.CommandLineUtils; -namespace Microsoft.AspNetCore.Blazor.DevServer +namespace Microsoft.AspNetCore.Components.WebAssembly.DevServer { internal class Program { diff --git a/src/Components/Blazor/DevServer/src/Server/Program.cs b/src/Components/WebAssembly/DevServer/src/Server/Program.cs similarity index 60% rename from src/Components/Blazor/DevServer/src/Server/Program.cs rename to src/Components/WebAssembly/DevServer/src/Server/Program.cs index 38f3b0d00526dadc7e04fac423a7cac792fc463b..66ee3c162ed4e81893ad3499260aae91683a285c 100644 --- a/src/Components/Blazor/DevServer/src/Server/Program.cs +++ b/src/Components/WebAssembly/DevServer/src/Server/Program.cs @@ -1,17 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Threading; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -namespace Microsoft.AspNetCore.Blazor.DevServer.Server +namespace Microsoft.AspNetCore.Components.WebAssembly.DevServer.Server { // This project is a CLI tool, so we don't expect anyone to reference it // as a runtime library. As such we consider it reasonable to mark the @@ -27,17 +24,22 @@ namespace Microsoft.AspNetCore.Blazor.DevServer.Server /// </summary> public static IHost BuildWebHost(string[] args) => Host.CreateDefaultBuilder(args) - .ConfigureHostConfiguration(cb => { + .ConfigureHostConfiguration(config => + { var applicationPath = args.SkipWhile(a => a != "--applicationpath").Skip(1).FirstOrDefault(); - var name = Path.ChangeExtension(applicationPath,".StaticWebAssets.xml"); + var applicationDirectory = Path.GetDirectoryName(applicationPath); + var name = Path.ChangeExtension(applicationPath, ".StaticWebAssets.xml"); - if (name != null) + var inMemoryConfiguration = new Dictionary<string, string> { - cb.AddInMemoryCollection(new Dictionary<string, string> - { - [WebHostDefaults.StaticWebAssetsKey] = name - }); - } + [WebHostDefaults.EnvironmentKey] = "Development", + ["Logging:LogLevel:Microsoft"] = "Warning", + ["Logging:LogLevel:Microsoft.Hosting.Lifetime"] = "Information", + [WebHostDefaults.StaticWebAssetsKey] = name, + }; + + config.AddInMemoryCollection(inMemoryConfiguration); + config.AddJsonFile(Path.Combine(applicationDirectory, "blazor-devserversettings.json"), optional: true, reloadOnChange: true); }) .ConfigureWebHostDefaults(webBuilder => { diff --git a/src/Components/Blazor/DevServer/src/Server/Startup.cs b/src/Components/WebAssembly/DevServer/src/Server/Startup.cs similarity index 51% rename from src/Components/Blazor/DevServer/src/Server/Startup.cs rename to src/Components/WebAssembly/DevServer/src/Server/Startup.cs index ca35836f0c740ce7a215b556136f6dc5942a074d..2541597dca4ea61e96d3dac2f579a22d4d508f10 100644 --- a/src/Components/Blazor/DevServer/src/Server/Startup.cs +++ b/src/Components/WebAssembly/DevServer/src/Server/Startup.cs @@ -1,19 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.IO; -using System.Linq; -using System.Net.Mime; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Components.Server; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.ResponseCompression; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -namespace Microsoft.AspNetCore.Blazor.DevServer.Server +namespace Microsoft.AspNetCore.Components.WebAssembly.DevServer.Server { internal class Startup { @@ -27,56 +20,31 @@ namespace Microsoft.AspNetCore.Blazor.DevServer.Server public void ConfigureServices(IServiceCollection services) { services.AddRouting(); - - services.AddResponseCompression(options => - { - options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] - { - MediaTypeNames.Application.Octet, - "application/wasm", - }); - }); } - public void Configure(IApplicationBuilder app, IWebHostEnvironment environment, IConfiguration configuration) + public void Configure(IApplicationBuilder app, IConfiguration configuration) { - var applicationAssemblyFullPath = ResolveApplicationAssemblyFullPath(); - app.UseDeveloperExceptionPage(); - app.UseResponseCompression(); EnableConfiguredPathbase(app, configuration); - app.UseBlazorDebugging(); + app.UseWebAssemblyDebugging(); - app.UseStaticFiles(); - app.UseClientSideBlazorFiles(applicationAssemblyFullPath); + app.UseBlazorFrameworkFiles(); + app.UseStaticFiles(new StaticFileOptions + { + // In development, serve everything, as there's no other way to configure it. + // In production, developers are responsible for configuring their own production server + ServeUnknownFileTypes = true, + }); app.UseRouting(); app.UseEndpoints(endpoints => { - endpoints.MapFallbackToClientSideBlazor(applicationAssemblyFullPath, "index.html"); + endpoints.MapFallbackToFile("index.html"); }); } - private string ResolveApplicationAssemblyFullPath() - { - const string applicationPathKey = "applicationpath"; - var configuredApplicationPath = Configuration.GetValue<string>(applicationPathKey); - if (string.IsNullOrEmpty(configuredApplicationPath)) - { - throw new InvalidOperationException($"No value was supplied for the required option '{applicationPathKey}'."); - } - - var resolvedApplicationPath = Path.GetFullPath(configuredApplicationPath); - if (!File.Exists(resolvedApplicationPath)) - { - throw new InvalidOperationException($"Application assembly not found at {resolvedApplicationPath}."); - } - - return resolvedApplicationPath; - } - private static void EnableConfiguredPathbase(IApplicationBuilder app, IConfiguration configuration) { var pathBase = configuration.GetValue<string>("pathbase"); diff --git a/src/Components/Blazor/DevServer/src/build/Microsoft.AspNetCore.Blazor.DevServer.targets b/src/Components/WebAssembly/DevServer/src/build/Microsoft.AspNetCore.Components.WebAssembly.DevServer.targets similarity index 72% rename from src/Components/Blazor/DevServer/src/build/Microsoft.AspNetCore.Blazor.DevServer.targets rename to src/Components/WebAssembly/DevServer/src/build/Microsoft.AspNetCore.Components.WebAssembly.DevServer.targets index 2db2b153fbf1670b3db1a7dd1eeb16f48c908ab3..09b78cf7196ddacfe08af7b4c268adf1947c438f 100644 --- a/src/Components/Blazor/DevServer/src/build/Microsoft.AspNetCore.Blazor.DevServer.targets +++ b/src/Components/WebAssembly/DevServer/src/build/Microsoft.AspNetCore.Components.WebAssembly.DevServer.targets @@ -2,6 +2,6 @@ <PropertyGroup> <_BlazorDevServerDll>$(MSBuildThisFileDirectory)../tools/blazor-devserver.dll</_BlazorDevServerDll> <RunCommand>dotnet</RunCommand> - <RunArguments>"$(_BlazorDevServerDll)" serve --applicationpath "$(MSBuildProjectDirectory)/$(OutputPath)$(TargetFileName)"</RunArguments> + <RunArguments>"$(_BlazorDevServerDll)" serve --applicationpath "$(TargetPath)"</RunArguments> </PropertyGroup> </Project> diff --git a/src/Components/WebAssembly/DevServer/src/runtimeconfig.template.json b/src/Components/WebAssembly/DevServer/src/runtimeconfig.template.json new file mode 100644 index 0000000000000000000000000000000000000000..f022b7ffce12faf836b7ce0f45660109445c5d51 --- /dev/null +++ b/src/Components/WebAssembly/DevServer/src/runtimeconfig.template.json @@ -0,0 +1,3 @@ +{ + "rollForwardOnNoCandidateFx": 2 +} \ No newline at end of file diff --git a/src/Components/WebAssembly/Directory.Build.props b/src/Components/WebAssembly/Directory.Build.props new file mode 100644 index 0000000000000000000000000000000000000000..cf2cedc9f865db4d93557009a7a82d372acf6e02 --- /dev/null +++ b/src/Components/WebAssembly/Directory.Build.props @@ -0,0 +1,7 @@ +<Project> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" /> + <PropertyGroup> + <!-- Override version labels --> + <VersionPrefix>$(ComponentsWebAssemblyVersionPrefix)</VersionPrefix> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/src/Components/Blazor/Mono.WebAssembly.Interop/src/InternalCalls.cs b/src/Components/WebAssembly/JSInterop/src/InternalCalls.cs similarity index 87% rename from src/Components/Blazor/Mono.WebAssembly.Interop/src/InternalCalls.cs rename to src/Components/WebAssembly/JSInterop/src/InternalCalls.cs index 60c0cdc42913ae2e8f2034afe299cf8f91555372..60e19f025c6de8f0434b79ad9adcaa4368ff207f 100644 --- a/src/Components/Blazor/Mono.WebAssembly.Interop/src/InternalCalls.cs +++ b/src/Components/WebAssembly/JSInterop/src/InternalCalls.cs @@ -7,12 +7,13 @@ namespace WebAssembly.JSInterop { /// <summary> /// Methods that map to the functions compiled into the Mono WebAssembly runtime, - /// as defined by 'mono_add_internal_call' calls in driver.c + /// as defined by 'mono_add_internal_call' calls in driver.c. /// </summary> - internal class InternalCalls + internal static class InternalCalls { // The exact namespace, type, and method names must match the corresponding entries // in driver.c in the Mono distribution + /// See: https://github.com/mono/mono/blob/90574987940959fe386008a850982ea18236a533/sdks/wasm/src/driver.c#L318-L319 // We're passing asyncHandle by ref not because we want it to be writable, but so it gets // passed as a pointer (4 bytes). We can pass 4-byte values, but not 8-byte ones. diff --git a/src/Components/Blazor/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj b/src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj similarity index 72% rename from src/Components/Blazor/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj rename to src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj index ea714b2d92dc9c17f6299c89a4229ee27b138b6b..5f291a8faf979e5f8e532dfbbb80480cddc32dbf 100644 --- a/src/Components/Blazor/Mono.WebAssembly.Interop/src/Mono.WebAssembly.Interop.csproj +++ b/src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj @@ -2,12 +2,13 @@ <PropertyGroup> <TargetFramework>netstandard2.1</TargetFramework> - <Description>Abstractions and features for interop between Mono WebAssembly and JavaScript code.</Description> + <Description>Abstractions and features for interop between .NET WebAssembly and JavaScript code.</Description> <PackageTags>wasm;javascript;interop</PackageTags> <GenerateDocumentationFile>true</GenerateDocumentationFile> <IsPackable>true</IsPackable> <IsShipping>true</IsShipping> <HasReferenceAssembly>false</HasReferenceAssembly> + <GenerateDocumentationFile>true</GenerateDocumentationFile> </PropertyGroup> <ItemGroup> diff --git a/src/Components/Blazor/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs similarity index 51% rename from src/Components/Blazor/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs rename to src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs index 654263a12331545912510d54c4cbf2a9407f2884..77cd0bac8d912505af3b472272d3bea0315ec4c4 100644 --- a/src/Components/Blazor/Mono.WebAssembly.Interop/src/MonoWebAssemblyJSRuntime.cs +++ b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSRuntime.cs @@ -1,39 +1,18 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Text.Json; -using Microsoft.JSInterop; using Microsoft.JSInterop.Infrastructure; using WebAssembly.JSInterop; -namespace Mono.WebAssembly.Interop +namespace Microsoft.JSInterop.WebAssembly { /// <summary> /// Provides methods for invoking JavaScript functions for applications running /// on the Mono WebAssembly runtime. /// </summary> - public class MonoWebAssemblyJSRuntime : JSInProcessRuntime + public abstract class WebAssemblyJSRuntime : JSInProcessRuntime { - /// <summary> - /// Gets the <see cref="MonoWebAssemblyJSRuntime"/> used to perform operations using <see cref="DotNetDispatcher"/>. - /// </summary> - private static MonoWebAssemblyJSRuntime Instance { get; set; } - - /// <summary> - /// Initializes the <see cref="MonoWebAssemblyJSRuntime"/> to be used to perform operations using <see cref="DotNetDispatcher"/>. - /// </summary> - /// <param name="jsRuntime">The <see cref="MonoWebAssemblyJSRuntime"/> instance.</param> - protected static void Initialize(MonoWebAssemblyJSRuntime jsRuntime) - { - if (Instance != null) - { - throw new InvalidOperationException("MonoWebAssemblyJSRuntime has already been initialized."); - } - - Instance = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); - } - /// <inheritdoc /> protected override string InvokeJS(string identifier, string argsJson) { @@ -50,40 +29,6 @@ namespace Mono.WebAssembly.Interop InternalCalls.InvokeJSMarshalled(out _, ref asyncHandle, identifier, argsJson); } - // Invoked via Mono's JS interop mechanism (invoke_method) - private static string InvokeDotNet(string assemblyName, string methodIdentifier, string dotNetObjectId, string argsJson) - { - var callInfo = new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), callId: null); - return DotNetDispatcher.Invoke(Instance, callInfo, argsJson); - } - - // Invoked via Mono's JS interop mechanism (invoke_method) - private static void EndInvokeJS(string argsJson) - => DotNetDispatcher.EndInvokeJS(Instance, argsJson); - - // Invoked via Mono's JS interop mechanism (invoke_method) - private static void BeginInvokeDotNet(string callId, string assemblyNameOrDotNetObjectId, string methodIdentifier, string argsJson) - { - // Figure out whether 'assemblyNameOrDotNetObjectId' is the assembly name or the instance ID - // We only need one for any given call. This helps to work around the limitation that we can - // only pass a maximum of 4 args in a call from JS to Mono WebAssembly. - string assemblyName; - long dotNetObjectId; - if (char.IsDigit(assemblyNameOrDotNetObjectId[0])) - { - dotNetObjectId = long.Parse(assemblyNameOrDotNetObjectId); - assemblyName = null; - } - else - { - dotNetObjectId = default; - assemblyName = assemblyNameOrDotNetObjectId; - } - - var callInfo = new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId, callId); - DotNetDispatcher.BeginInvokeDotNet(Instance, callInfo, argsJson); - } - protected override void EndInvokeDotNet(DotNetInvocationInfo callInfo, in DotNetInvocationResult dispatchResult) { // For failures, the common case is to call EndInvokeDotNet with the Exception object. @@ -97,40 +42,38 @@ namespace Mono.WebAssembly.Interop BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", args); } - #region Custom MonoWebAssemblyJSRuntime methods - /// <summary> /// Invokes the JavaScript function registered with the specified identifier. /// </summary> - /// <typeparam name="TRes">The .NET type corresponding to the function's return value type.</typeparam> + /// <typeparam name="TResult">The .NET type corresponding to the function's return value type.</typeparam> /// <param name="identifier">The identifier used when registering the target function.</param> /// <returns>The result of the function invocation.</returns> - public TRes InvokeUnmarshalled<TRes>(string identifier) - => InvokeUnmarshalled<object, object, object, TRes>(identifier, null, null, null); + public TResult InvokeUnmarshalled<TResult>(string identifier) + => InvokeUnmarshalled<object, object, object, TResult>(identifier, null, null, null); /// <summary> /// Invokes the JavaScript function registered with the specified identifier. /// </summary> /// <typeparam name="T0">The type of the first argument.</typeparam> - /// <typeparam name="TRes">The .NET type corresponding to the function's return value type.</typeparam> + /// <typeparam name="TResult">The .NET type corresponding to the function's return value type.</typeparam> /// <param name="identifier">The identifier used when registering the target function.</param> /// <param name="arg0">The first argument.</param> /// <returns>The result of the function invocation.</returns> - public TRes InvokeUnmarshalled<T0, TRes>(string identifier, T0 arg0) - => InvokeUnmarshalled<T0, object, object, TRes>(identifier, arg0, null, null); + public TResult InvokeUnmarshalled<T0, TResult>(string identifier, T0 arg0) + => InvokeUnmarshalled<T0, object, object, TResult>(identifier, arg0, null, null); /// <summary> /// Invokes the JavaScript function registered with the specified identifier. /// </summary> /// <typeparam name="T0">The type of the first argument.</typeparam> /// <typeparam name="T1">The type of the second argument.</typeparam> - /// <typeparam name="TRes">The .NET type corresponding to the function's return value type.</typeparam> + /// <typeparam name="TResult">The .NET type corresponding to the function's return value type.</typeparam> /// <param name="identifier">The identifier used when registering the target function.</param> /// <param name="arg0">The first argument.</param> /// <param name="arg1">The second argument.</param> /// <returns>The result of the function invocation.</returns> - public TRes InvokeUnmarshalled<T0, T1, TRes>(string identifier, T0 arg0, T1 arg1) - => InvokeUnmarshalled<T0, T1, object, TRes>(identifier, arg0, arg1, null); + public TResult InvokeUnmarshalled<T0, T1, TResult>(string identifier, T0 arg0, T1 arg1) + => InvokeUnmarshalled<T0, T1, object, TResult>(identifier, arg0, arg1, null); /// <summary> /// Invokes the JavaScript function registered with the specified identifier. @@ -138,20 +81,18 @@ namespace Mono.WebAssembly.Interop /// <typeparam name="T0">The type of the first argument.</typeparam> /// <typeparam name="T1">The type of the second argument.</typeparam> /// <typeparam name="T2">The type of the third argument.</typeparam> - /// <typeparam name="TRes">The .NET type corresponding to the function's return value type.</typeparam> + /// <typeparam name="TResult">The .NET type corresponding to the function's return value type.</typeparam> /// <param name="identifier">The identifier used when registering the target function.</param> /// <param name="arg0">The first argument.</param> /// <param name="arg1">The second argument.</param> /// <param name="arg2">The third argument.</param> /// <returns>The result of the function invocation.</returns> - public TRes InvokeUnmarshalled<T0, T1, T2, TRes>(string identifier, T0 arg0, T1 arg1, T2 arg2) + public TResult InvokeUnmarshalled<T0, T1, T2, TResult>(string identifier, T0 arg0, T1 arg1, T2 arg2) { - var result = InternalCalls.InvokeJSUnmarshalled<T0, T1, T2, TRes>(out var exception, identifier, arg0, arg1, arg2); + var result = InternalCalls.InvokeJSUnmarshalled<T0, T1, T2, TResult>(out var exception, identifier, arg0, arg1, arg2); return exception != null ? throw new JSException(exception) : result; } - - #endregion } } diff --git a/src/Components/WebAssembly/Server/src/ComponentsWebAssemblyApplicationBuilderExtensions.cs b/src/Components/WebAssembly/Server/src/ComponentsWebAssemblyApplicationBuilderExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..534dd3502dcc582f90721a9372add2bb69a26f34 --- /dev/null +++ b/src/Components/WebAssembly/Server/src/ComponentsWebAssemblyApplicationBuilderExtensions.cs @@ -0,0 +1,121 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Mime; +using Microsoft.AspNetCore.Components.WebAssembly.Server; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Builder +{ + /// <summary> + /// Extensions for mapping Blazor WebAssembly applications. + /// </summary> + public static class ComponentsWebAssemblyApplicationBuilderExtensions + { + /// <summary> + /// Configures the application to serve Blazor WebAssembly framework files from the path <paramref name="pathPrefix"/>. This path must correspond to a referenced Blazor WebAssembly application project. + /// </summary> + /// <param name="builder">The <see cref="IApplicationBuilder"/>.</param> + /// <param name="pathPrefix">The <see cref="PathString"/> that indicates the prefix for the Blazor WebAssembly application.</param> + /// <returns>The <see cref="IApplicationBuilder"/></returns> + public static IApplicationBuilder UseBlazorFrameworkFiles(this IApplicationBuilder builder, PathString pathPrefix) + { + if (builder is null) + { + throw new ArgumentNullException(nameof(builder)); + } + + var webHostEnvironment = builder.ApplicationServices.GetRequiredService<IWebHostEnvironment>(); + + var options = CreateStaticFilesOptions(webHostEnvironment.WebRootFileProvider); + + builder.MapWhen(ctx => ctx.Request.Path.StartsWithSegments(pathPrefix, out var rest) && rest.StartsWithSegments("/_framework") && + !rest.StartsWithSegments("/_framework/blazor.server.js"), + subBuilder => + { + subBuilder.Use(async (context, next) => + { + context.Response.Headers.Append("Blazor-Environment", webHostEnvironment.EnvironmentName); + + await next(); + }); + + subBuilder.UseMiddleware<ContentEncodingNegotiator>(); + + subBuilder.UseStaticFiles(options); + }); + + return builder; + } + + /// <summary> + /// Configures the application to serve Blazor WebAssembly framework files from the root path "/". + /// </summary> + /// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/>.</param> + /// <returns>The <see cref="IApplicationBuilder"/></returns> + public static IApplicationBuilder UseBlazorFrameworkFiles(this IApplicationBuilder applicationBuilder) => + UseBlazorFrameworkFiles(applicationBuilder, default); + + private static StaticFileOptions CreateStaticFilesOptions(IFileProvider webRootFileProvider) + { + var options = new StaticFileOptions(); + options.FileProvider = webRootFileProvider; + var contentTypeProvider = new FileExtensionContentTypeProvider(); + AddMapping(contentTypeProvider, ".dll", MediaTypeNames.Application.Octet); + // We unconditionally map pdbs as there will be no pdbs in the output folder for + // release builds unless BlazorEnableDebugging is explicitly set to true. + AddMapping(contentTypeProvider, ".pdb", MediaTypeNames.Application.Octet); + AddMapping(contentTypeProvider, ".br", MediaTypeNames.Application.Octet); + AddMapping(contentTypeProvider, ".dat", MediaTypeNames.Application.Octet); + + options.ContentTypeProvider = contentTypeProvider; + + // Static files middleware will try to use application/x-gzip as the content + // type when serving a file with a gz extension. We need to correct that before + // sending the file. + options.OnPrepareResponse = fileContext => + { + // At this point we mapped something from the /_framework + fileContext.Context.Response.Headers.Append(HeaderNames.CacheControl, "no-cache"); + + var requestPath = fileContext.Context.Request.Path; + var fileExtension = Path.GetExtension(requestPath.Value); + if (string.Equals(fileExtension, ".gz") || string.Equals(fileExtension, ".br")) + { + // When we are serving framework files (under _framework/ we perform content negotiation + // on the accept encoding and replace the path with <<original>>.gz|br if we can serve gzip or brotli content + // respectively. + // Here we simply calculate the original content type by removing the extension and apply it + // again. + // When we revisit this, we should consider calculating the original content type and storing it + // in the request along with the original target path so that we don't have to calculate it here. + var originalPath = Path.GetFileNameWithoutExtension(requestPath.Value); + if (contentTypeProvider.TryGetContentType(originalPath, out var originalContentType)) + { + fileContext.Context.Response.ContentType = originalContentType; + } + } + }; + + return options; + } + + private static void AddMapping(FileExtensionContentTypeProvider provider, string name, string mimeType) + { + if (!provider.Mappings.ContainsKey(name)) + { + provider.Mappings.Add(name, mimeType); + } + } + } +} diff --git a/src/Components/WebAssembly/Server/src/ContentEncodingNegotiator.cs b/src/Components/WebAssembly/Server/src/ContentEncodingNegotiator.cs new file mode 100644 index 0000000000000000000000000000000000000000..7caddb2f7575c60e16bb93979d1a4db17550526a --- /dev/null +++ b/src/Components/WebAssembly/Server/src/ContentEncodingNegotiator.cs @@ -0,0 +1,122 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Server +{ + internal class ContentEncodingNegotiator + { + // List of encodings by preference order with their associated extension so that we can easily handle "*". + private static readonly StringSegment[] _preferredEncodings = + new StringSegment[] { "br", "gzip" }; + + private static readonly Dictionary<StringSegment, string> _encodingExtensionMap = new Dictionary<StringSegment, string>(StringSegmentComparer.OrdinalIgnoreCase) + { + ["br"] = ".br", + ["gzip"] = ".gz" + }; + + private readonly RequestDelegate _next; + private readonly IWebHostEnvironment _webHostEnvironment; + + public ContentEncodingNegotiator(RequestDelegate next, IWebHostEnvironment webHostEnvironment) + { + _next = next; + _webHostEnvironment = webHostEnvironment; + } + + public Task InvokeAsync(HttpContext context) + { + NegotiateEncoding(context); + return _next(context); + } + + private void NegotiateEncoding(HttpContext context) + { + var accept = context.Request.Headers[HeaderNames.AcceptEncoding]; + + if (StringValues.IsNullOrEmpty(accept)) + { + return; + } + + if (!StringWithQualityHeaderValue.TryParseList(accept, out var encodings) || encodings.Count == 0) + { + return; + } + + var selectedEncoding = StringSegment.Empty; + var selectedEncodingQuality = .0; + + foreach (var encoding in encodings) + { + var encodingName = encoding.Value; + var quality = encoding.Quality.GetValueOrDefault(1); + + if (quality >= double.Epsilon && quality >= selectedEncodingQuality) + { + if (quality == selectedEncodingQuality) + { + selectedEncoding = PickPreferredEncoding(context, selectedEncoding, encoding); + } + else if (_encodingExtensionMap.TryGetValue(encodingName, out var encodingExtension) && ResourceExists(context, encodingExtension)) + { + selectedEncoding = encodingName; + selectedEncodingQuality = quality; + } + + if (StringSegment.Equals("*", encodingName, StringComparison.Ordinal)) + { + // If we *, pick the first preferrent encoding for which a resource exists. + selectedEncoding = PickPreferredEncoding(context, default, encoding); + selectedEncodingQuality = quality; + } + + if (StringSegment.Equals("identity", encodingName, StringComparison.OrdinalIgnoreCase)) + { + selectedEncoding = StringSegment.Empty; + selectedEncodingQuality = quality; + } + } + } + + if (_encodingExtensionMap.TryGetValue(selectedEncoding, out var extension)) + { + context.Request.Path = context.Request.Path + extension; + context.Response.Headers[HeaderNames.ContentEncoding] = selectedEncoding.Value; + context.Response.Headers.Append(HeaderNames.Vary, HeaderNames.ContentEncoding); + } + + return; + + StringSegment PickPreferredEncoding(HttpContext context, StringSegment selectedEncoding, StringWithQualityHeaderValue encoding) + { + foreach (var preferredEncoding in _preferredEncodings) + { + if (preferredEncoding == selectedEncoding) + { + return selectedEncoding; + } + + if ((preferredEncoding == encoding.Value || encoding.Value == "*") && ResourceExists(context, _encodingExtensionMap[preferredEncoding])) + { + return preferredEncoding; + } + } + + return StringSegment.Empty; + } + } + + private bool ResourceExists(HttpContext context, string extension) => + _webHostEnvironment.WebRootFileProvider.GetFileInfo(context.Request.Path + extension).Exists; + } +} diff --git a/src/Components/WebAssembly/Server/src/DebugProxyLauncher.cs b/src/Components/WebAssembly/Server/src/DebugProxyLauncher.cs new file mode 100644 index 0000000000000000000000000000000000000000..1a1ba6b85e1ecc3d2cffe708d72d048858807e35 --- /dev/null +++ b/src/Components/WebAssembly/Server/src/DebugProxyLauncher.cs @@ -0,0 +1,132 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.CommandLineUtils; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Builder +{ + internal static class DebugProxyLauncher + { + private static readonly object LaunchLock = new object(); + private static readonly TimeSpan DebugProxyLaunchTimeout = TimeSpan.FromSeconds(10); + private static Task<string> LaunchedDebugProxyUrl; + private static readonly Regex NowListeningRegex = new Regex(@"^\s*Now listening on: (?<url>.*)$", RegexOptions.None, TimeSpan.FromSeconds(10)); + private static readonly Regex ApplicationStartedRegex = new Regex(@"^\s*Application started\. Press Ctrl\+C to shut down\.$", RegexOptions.None, TimeSpan.FromSeconds(10)); + + public static Task<string> EnsureLaunchedAndGetUrl(IServiceProvider serviceProvider) + { + lock (LaunchLock) + { + if (LaunchedDebugProxyUrl == null) + { + LaunchedDebugProxyUrl = LaunchAndGetUrl(serviceProvider); + } + + return LaunchedDebugProxyUrl; + } + } + + private static async Task<string> LaunchAndGetUrl(IServiceProvider serviceProvider) + { + var tcs = new TaskCompletionSource<string>(); + + var environment = serviceProvider.GetRequiredService<IWebHostEnvironment>(); + var executablePath = LocateDebugProxyExecutable(environment); + var muxerPath = DotNetMuxer.MuxerPathOrDefault(); + var ownerPid = Process.GetCurrentProcess().Id; + var processStartInfo = new ProcessStartInfo + { + FileName = muxerPath, + Arguments = $"exec \"{executablePath}\" --owner-pid {ownerPid}", + UseShellExecute = false, + RedirectStandardOutput = true, + }; + RemoveUnwantedEnvironmentVariables(processStartInfo.Environment); + + var debugProxyProcess = Process.Start(processStartInfo); + CompleteTaskWhenServerIsReady(debugProxyProcess, tcs); + + new CancellationTokenSource(DebugProxyLaunchTimeout).Token.Register(() => + { + tcs.TrySetException(new TimeoutException($"Failed to start the debug proxy within the timeout period of {DebugProxyLaunchTimeout.TotalSeconds} seconds.")); + }); + + return await tcs.Task; + } + + private static void RemoveUnwantedEnvironmentVariables(IDictionary<string, string> environment) + { + // Generally we expect to pass through most environment variables, since dotnet might + // need them for arbitrary reasons to function correctly. However, we specifically don't + // want to pass through any ASP.NET Core hosting related ones, since the child process + // shouldn't be trying to use the same port numbers, etc. In particular we need to break + // the association with IISExpress and the MS-ASPNETCORE-TOKEN check. + // For more context on this, see https://github.com/dotnet/aspnetcore/issues/20308. + var keysToRemove = environment.Keys.Where(key => key.StartsWith("ASPNETCORE_")).ToList(); + foreach (var key in keysToRemove) + { + environment.Remove(key); + } + } + + private static string LocateDebugProxyExecutable(IWebHostEnvironment environment) + { + var assembly = Assembly.Load(environment.ApplicationName); + var debugProxyPath = Path.Combine( + Path.GetDirectoryName(assembly.Location), + "BlazorDebugProxy", + "Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.dll"); + + if (!File.Exists(debugProxyPath)) + { + throw new FileNotFoundException( + $"Cannot start debug proxy because it cannot be found at '{debugProxyPath}'"); + } + + return debugProxyPath; + } + + private static void CompleteTaskWhenServerIsReady(Process aspNetProcess, TaskCompletionSource<string> taskCompletionSource) + { + string capturedUrl = null; + aspNetProcess.OutputDataReceived += OnOutputDataReceived; + aspNetProcess.BeginOutputReadLine(); + + void OnOutputDataReceived(object sender, DataReceivedEventArgs eventArgs) + { + if (ApplicationStartedRegex.IsMatch(eventArgs.Data)) + { + aspNetProcess.OutputDataReceived -= OnOutputDataReceived; + if (!string.IsNullOrEmpty(capturedUrl)) + { + taskCompletionSource.TrySetResult(capturedUrl); + } + else + { + taskCompletionSource.TrySetException(new InvalidOperationException( + "The application started listening without first advertising a URL")); + } + } + else + { + var match = NowListeningRegex.Match(eventArgs.Data); + if (match.Success) + { + capturedUrl = match.Groups["url"].Value; + } + } + } + } + } +} diff --git a/src/Components/WebAssembly/Server/src/Microsoft.AspNetCore.Components.WebAssembly.Server.csproj b/src/Components/WebAssembly/Server/src/Microsoft.AspNetCore.Components.WebAssembly.Server.csproj new file mode 100644 index 0000000000000000000000000000000000000000..0432f421fe27c4cdf52a746dcf37eb66433d2a40 --- /dev/null +++ b/src/Components/WebAssembly/Server/src/Microsoft.AspNetCore.Components.WebAssembly.Server.csproj @@ -0,0 +1,56 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> + <Description>Runtime server features for ASP.NET Core Blazor applications.</Description> + <IsShippingPackage>true</IsShippingPackage> + <HasReferenceAssembly>false</HasReferenceAssembly> + + <!-- We're deliberately bundling assemblies as content files. Suppress the warning. --> + <NoWarn>$(NoWarn);NU5100</NoWarn> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + </PropertyGroup> + + <ItemGroup> + <Reference Include="Microsoft.AspNetCore.StaticFiles" /> + </ItemGroup> + + <ItemGroup> + <InternalsVisibleTo Include="Microsoft.AspNetCore.Components.WebAssembly.Server.Tests" /> + </ItemGroup> + + <ItemGroup> + <Compile Include="$(ComponentsSharedSourceRoot)\src\CacheHeaderSettings.cs" Link="Shared\CacheHeaderSettings.cs" /> + <Compile Include="$(SharedSourceRoot)\CommandLineUtils\Utilities\DotNetMuxer.cs" Link="Shared\DotNetMuxer.cs" /> + + <!-- Ensure debug proxy is built first, but don't create an actual reference, since we don't want its transitive dependencies. --> + <ProjectReference + Include="..\..\DebugProxy\src\Microsoft.AspNetCore.Components.WebAssembly.DebugProxy.csproj" + ReferenceOutputAssembly="false" /> + + <Content Include="build\**" Pack="true" PackagePath="build\%(RecursiveDir)%(FileName)%(Extension)" /> + </ItemGroup> + + <!-- Emit debug proxy binaries to output directory. This lets us launch it as a separate process while keeping the build output self-contained. --> + <Target Name="IncludeDebugProxyBinariesAsContent" BeforeTargets="AssignTargetPaths"> + <ItemGroup> + <DebugProxyBinaries Include="..\..\DebugProxy\src\bin\$(Configuration)\$(DefaultNetCoreTargetFramework)\**" /> + + <!-- + For when we're building a package, we use Pack and PackagePath to bundle the debug proxy binaries into 'tools'. + Then we have a custom build target that converts these items into content files in consuming projects. We *don't* + use PackageCopyToOutput because that creates entries that show up in Solution Explorer in consuming projects. + + For when we're consuming this from source in this repo, we use Link and CopyToOutputDirectory to produce the + same effect without having the custom build target. + --> + <Content + Include="@(DebugProxyBinaries)" + Pack="true" + PackagePath="tools\BlazorDebugProxy\%(RecursiveDir)%(FileName)%(Extension)" + Link="BlazorDebugProxy\%(RecursiveDir)%(FileName)%(Extension)" + CopyToOutputDirectory="PreserveNewest" /> + </ItemGroup> + </Target> + +</Project> diff --git a/src/Components/WebAssembly/Server/src/WebAssemblyNetDebugProxyAppBuilderExtensions.cs b/src/Components/WebAssembly/Server/src/WebAssemblyNetDebugProxyAppBuilderExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..2ad827c336be608ad5c85fd7c859cb9934ba8677 --- /dev/null +++ b/src/Components/WebAssembly/Server/src/WebAssemblyNetDebugProxyAppBuilderExtensions.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Net; + +namespace Microsoft.AspNetCore.Builder +{ + /// <summary> + /// Provides infrastructure for debugging Blazor WebAssembly applications. + /// </summary> + public static class WebAssemblyNetDebugProxyAppBuilderExtensions + { + /// <summary> + /// Adds middleware for needed for debugging Blazor WebAssembly applications + /// inside Chromium dev tools. + /// </summary> + public static void UseWebAssemblyDebugging(this IApplicationBuilder app) + { + app.Map("/_framework/debug", app => + { + app.Use(async (context, next) => + { + var debugProxyBaseUrl = await DebugProxyLauncher.EnsureLaunchedAndGetUrl(context.RequestServices); + var requestPath = context.Request.Path.ToString(); + if (requestPath == string.Empty) + { + requestPath = "/"; + } + + // Although we could redirect for every URL we see here, we filter the allowed set + // to ensure this doesn't get misused as some kind of more general redirector + switch (requestPath) + { + case "/": + case "/ws-proxy": + context.Response.Redirect($"{debugProxyBaseUrl}{requestPath}{context.Request.QueryString}"); + break; + default: + context.Response.StatusCode = (int)HttpStatusCode.NotFound; + break; + } + }); + }); + } + } +} diff --git a/src/Components/WebAssembly/Server/src/build/Microsoft.AspNetCore.Components.WebAssembly.Server.targets b/src/Components/WebAssembly/Server/src/build/Microsoft.AspNetCore.Components.WebAssembly.Server.targets new file mode 100644 index 0000000000000000000000000000000000000000..ffa102691e2b9d6fe533db853cde933511626a11 --- /dev/null +++ b/src/Components/WebAssembly/Server/src/build/Microsoft.AspNetCore.Components.WebAssembly.Server.targets @@ -0,0 +1,16 @@ +<Project> + + <Target Name="_IncludeDebugProxyBinariesAsContent" BeforeTargets="AssignTargetPaths" + Condition="'$(BlazorWebAssemblyOmitDebugProxyOutput)' != 'true'"> + + <ItemGroup> + <_DebugProxyBinaries Include="$(MSBuildThisFileDirectory)..\tools\BlazorDebugProxy\**" /> + <Content + Include="@(_DebugProxyBinaries)" + Link="BlazorDebugProxy\%(RecursiveDir)%(FileName)%(Extension)" + CopyToOutputDirectory="PreserveNewest" /> + </ItemGroup> + + </Target> + +</Project> diff --git a/src/Components/WebAssembly/Server/test/ContentEncodingNegotiatorTests.cs b/src/Components/WebAssembly/Server/test/ContentEncodingNegotiatorTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..5976ef1613832d6f85c7fdae5d9b6acd8360357e --- /dev/null +++ b/src/Components/WebAssembly/Server/test/ContentEncodingNegotiatorTests.cs @@ -0,0 +1,233 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.FileProviders; +using Microsoft.Net.Http.Headers; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Server.Tests +{ + public class ContentEncodingNegotiatorTests + { + [Fact] + public async Task RespectsAcceptEncodingQuality() + { + var encoding = "gzip;q=0.5, deflate;q=0.3, br;q=0.2"; + var expectedPath = "/_framework/blazor.boot.json.gz"; + var expectedEncoding = "gzip"; + RequestDelegate next = (ctx) => Task.CompletedTask; + + var negotiator = new ContentEncodingNegotiator(next, CreateWebHostEnvironment()); + + var httpContext = new DefaultHttpContext(); + httpContext.Request.Path = "/_framework/blazor.boot.json"; + httpContext.Request.Headers.Append(HeaderNames.AcceptEncoding, encoding); + + await negotiator.InvokeAsync(httpContext); + + Assert.Equal(expectedPath, httpContext.Request.Path); + Assert.True(httpContext.Response.Headers.TryGetValue(HeaderNames.ContentEncoding, out var selectedEncoding)); + Assert.Equal(expectedEncoding, selectedEncoding); + Assert.True(httpContext.Response.Headers.TryGetValue(HeaderNames.Vary, out var varyHeader)); + Assert.Contains(HeaderNames.ContentEncoding, varyHeader.ToArray()); + } + + [Fact] + public async Task RespectsIdentity() + { + var encoding = "gzip;q=0.5, deflate;q=0.3, br;q=0.2, identity"; + var expectedPath = "/_framework/blazor.boot.json"; + RequestDelegate next = (ctx) => Task.CompletedTask; + + var negotiator = new ContentEncodingNegotiator(next, CreateWebHostEnvironment()); + + var httpContext = new DefaultHttpContext(); + httpContext.Request.Path = "/_framework/blazor.boot.json"; + httpContext.Request.Headers.Append(HeaderNames.AcceptEncoding, encoding); + + await negotiator.InvokeAsync(httpContext); + + Assert.Equal(expectedPath, httpContext.Request.Path); + Assert.False(httpContext.Response.Headers.TryGetValue(HeaderNames.ContentEncoding, out var selectedEncoding)); + Assert.False(httpContext.Response.Headers.TryGetValue(HeaderNames.Vary, out var varyHeader)); + } + + [Fact] + public async Task SkipsNonExistingFiles() + { + var encoding = "gzip;q=0.5, deflate;q=0.3, br"; + var expectedPath = "/_framework/blazor.boot.json.gz"; + var expectedEncoding = "gzip"; + RequestDelegate next = (ctx) => Task.CompletedTask; + + var negotiator = new ContentEncodingNegotiator(next, CreateWebHostEnvironment(brotliExists: false)); + + var httpContext = new DefaultHttpContext(); + httpContext.Request.Path = "/_framework/blazor.boot.json"; + httpContext.Request.Headers.Append(HeaderNames.AcceptEncoding, encoding); + + await negotiator.InvokeAsync(httpContext); + + Assert.Equal(expectedPath, httpContext.Request.Path); + Assert.True(httpContext.Response.Headers.TryGetValue(HeaderNames.ContentEncoding, out var selectedEncoding)); + Assert.Equal(expectedEncoding, selectedEncoding); + Assert.True(httpContext.Response.Headers.TryGetValue(HeaderNames.Vary, out var varyHeader)); + Assert.Contains(HeaderNames.ContentEncoding, varyHeader.ToArray()); + } + + [Fact] + public async Task UsesPreferredServerEncodingForEqualQualityValues() + { + var encoding = "gzip, deflate, br"; + var expectedPath = "/_framework/blazor.boot.json.br"; + var expectedEncoding = "br"; + RequestDelegate next = (ctx) => Task.CompletedTask; + + var negotiator = new ContentEncodingNegotiator(next, CreateWebHostEnvironment()); + + var httpContext = new DefaultHttpContext(); + httpContext.Request.Path = "/_framework/blazor.boot.json"; + httpContext.Request.Headers.Append(HeaderNames.AcceptEncoding, encoding); + + await negotiator.InvokeAsync(httpContext); + + Assert.Equal(expectedPath, httpContext.Request.Path); + Assert.True(httpContext.Response.Headers.TryGetValue(HeaderNames.ContentEncoding, out var selectedEncoding)); + Assert.Equal(expectedEncoding, selectedEncoding); + Assert.True(httpContext.Response.Headers.TryGetValue(HeaderNames.Vary, out var varyHeader)); + Assert.Contains(HeaderNames.ContentEncoding, varyHeader.ToArray()); + } + + [Fact] + public async Task SkipNonExistingFilesWhenSearchingForServerPreferencesPreferences() + { + var encoding = "gzip, deflate, br"; + var expectedPath = "/_framework/blazor.boot.json.gz"; + var expectedEncoding = "gzip"; + RequestDelegate next = (ctx) => Task.CompletedTask; + + var negotiator = new ContentEncodingNegotiator(next, CreateWebHostEnvironment(brotliExists: false)); + + var httpContext = new DefaultHttpContext(); + httpContext.Request.Path = "/_framework/blazor.boot.json"; + httpContext.Request.Headers.Append(HeaderNames.AcceptEncoding, encoding); + + await negotiator.InvokeAsync(httpContext); + + Assert.Equal(expectedPath, httpContext.Request.Path); + Assert.True(httpContext.Response.Headers.TryGetValue(HeaderNames.ContentEncoding, out var selectedEncoding)); + Assert.Equal(expectedEncoding, selectedEncoding); + Assert.True(httpContext.Response.Headers.TryGetValue(HeaderNames.Vary, out var varyHeader)); + Assert.Contains(HeaderNames.ContentEncoding, varyHeader.ToArray()); + } + + [Fact] + public async Task AnyUsesServerPreference() + { + var encoding = "*"; + var expectedPath = "/_framework/blazor.boot.json.br"; + var expectedEncoding = "br"; + RequestDelegate next = (ctx) => Task.CompletedTask; + + var negotiator = new ContentEncodingNegotiator(next, CreateWebHostEnvironment()); + + var httpContext = new DefaultHttpContext(); + httpContext.Request.Path = "/_framework/blazor.boot.json"; + httpContext.Request.Headers.Append(HeaderNames.AcceptEncoding, encoding); + + await negotiator.InvokeAsync(httpContext); + + Assert.Equal(expectedPath, httpContext.Request.Path); + Assert.True(httpContext.Response.Headers.TryGetValue(HeaderNames.ContentEncoding, out var selectedEncoding)); + Assert.Equal(expectedEncoding, selectedEncoding); + Assert.True(httpContext.Response.Headers.TryGetValue(HeaderNames.Vary, out var varyHeader)); + Assert.Contains(HeaderNames.ContentEncoding, varyHeader.ToArray()); + } + + [Fact] + public async Task AnySkipsNonExistingFiles() + { + var encoding = "*"; + var expectedPath = "/_framework/blazor.boot.json.gz"; + var expectedEncoding = "gzip"; + RequestDelegate next = (ctx) => Task.CompletedTask; + + var negotiator = new ContentEncodingNegotiator(next, CreateWebHostEnvironment(brotliExists: false)); + + var httpContext = new DefaultHttpContext(); + httpContext.Request.Path = "/_framework/blazor.boot.json"; + httpContext.Request.Headers.Append(HeaderNames.AcceptEncoding, encoding); + + await negotiator.InvokeAsync(httpContext); + + Assert.Equal(expectedPath, httpContext.Request.Path); + Assert.True(httpContext.Response.Headers.TryGetValue(HeaderNames.ContentEncoding, out var selectedEncoding)); + Assert.Equal(expectedEncoding, selectedEncoding); + Assert.True(httpContext.Response.Headers.TryGetValue(HeaderNames.Vary, out var varyHeader)); + Assert.Contains(HeaderNames.ContentEncoding, varyHeader.ToArray()); + } + + [Fact] + public async Task AnyDoesNotPickEncodingIfNoFilesFound() + { + var encoding = "*"; + var expectedPath = "/_framework/blazor.boot.json"; + RequestDelegate next = (ctx) => Task.CompletedTask; + + var negotiator = new ContentEncodingNegotiator(next, CreateWebHostEnvironment(gzipExists: false, brotliExists: false)); + + var httpContext = new DefaultHttpContext(); + httpContext.Request.Path = "/_framework/blazor.boot.json"; + httpContext.Request.Headers.Append(HeaderNames.AcceptEncoding, encoding); + + await negotiator.InvokeAsync(httpContext); + + Assert.Equal(expectedPath, httpContext.Request.Path); + Assert.False(httpContext.Response.Headers.TryGetValue(HeaderNames.ContentEncoding, out var selectedEncoding)); + Assert.False(httpContext.Response.Headers.TryGetValue(HeaderNames.Vary, out var varyHeader)); + } + + [Fact] + public async Task AnyRespectsServerPreference() + { + var encoding = "gzip;q=0.5, *;q=0.8, br;q=0.2"; + var expectedPath = "/_framework/blazor.boot.json.br"; + var expectedEncoding = "br"; + RequestDelegate next = (ctx) => Task.CompletedTask; + + var negotiator = new ContentEncodingNegotiator(next, CreateWebHostEnvironment()); + + var httpContext = new DefaultHttpContext(); + httpContext.Request.Path = "/_framework/blazor.boot.json"; + httpContext.Request.Headers.Append(HeaderNames.AcceptEncoding, encoding); + + await negotiator.InvokeAsync(httpContext); + + Assert.Equal(expectedPath, httpContext.Request.Path); + Assert.True(httpContext.Response.Headers.TryGetValue(HeaderNames.ContentEncoding, out var selectedEncoding)); + Assert.Equal(expectedEncoding, selectedEncoding); + Assert.True(httpContext.Response.Headers.TryGetValue(HeaderNames.Vary, out var varyHeader)); + Assert.Contains(HeaderNames.ContentEncoding, varyHeader.ToArray()); + } + + private static IWebHostEnvironment CreateWebHostEnvironment(bool gzipExists = true, bool brotliExists = true) + { + var gzMock = new Mock<IFileInfo>(); + gzMock.Setup(m => m.Exists).Returns(gzipExists); + var brMock = new Mock<IFileInfo>(); + brMock.Setup(m => m.Exists).Returns(brotliExists); + var fileProviderMock = new Mock<IFileProvider>(); + fileProviderMock.Setup(f => f.GetFileInfo("/_framework/blazor.boot.json.gz")).Returns(gzMock.Object); + fileProviderMock.Setup(f => f.GetFileInfo("/_framework/blazor.boot.json.br")).Returns(brMock.Object); + + var env = new Mock<IWebHostEnvironment>(); + env.Setup(e => e.WebRootFileProvider).Returns(fileProviderMock.Object); + + return env.Object; + } + } +} diff --git a/src/Components/WebAssembly/Server/test/Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj b/src/Components/WebAssembly/Server/test/Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj new file mode 100644 index 0000000000000000000000000000000000000000..292ec82b5d4aeba17c29ad71df9b3974221fa6c7 --- /dev/null +++ b/src/Components/WebAssembly/Server/test/Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + + <PropertyGroup> + <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" /> + <Reference Include="Microsoft.AspNetCore.Http" /> + </ItemGroup> + +</Project> diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/IAccessTokenProviderAccessor.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/IAccessTokenProviderAccessor.cs new file mode 100644 index 0000000000000000000000000000000000000000..5e20c27cfde5e3c3c89557f5e079cb38f8c93c9c --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/IAccessTokenProviderAccessor.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal +{ + /// <summary> + /// This is an internal API that supports the Microsoft.AspNetCore.Components.WebAssembly.Authentication + /// infrastructure and not subject to the same compatibility standards as public APIs. + /// It may be changed or removed without notice in any release. + /// </summary> + public interface IAccessTokenProviderAccessor + { + /// <summary> + /// This is an internal API that supports the Microsoft.AspNetCore.Components.WebAssembly.Authentication + /// infrastructure and not subject to the same compatibility standards as public APIs. + /// It may be changed or removed without notice in any release. + /// </summary> + IAccessTokenProvider TokenProvider { get; } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/IRemoteAuthenticationBuilder.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/IRemoteAuthenticationBuilder.cs new file mode 100644 index 0000000000000000000000000000000000000000..56fd2777eea500bfb56f00a692c600c634381aad --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/IRemoteAuthenticationBuilder.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// <summary> + /// An interface for configuring remote authentication services. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The remote authentication state type.</typeparam> + /// <typeparam name="TAccount">The account type.</typeparam> + public interface IRemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount> + where TRemoteAuthenticationState : RemoteAuthenticationState + where TAccount : RemoteUserAccount + { + IServiceCollection Services { get; } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/IRemoteAuthenticationPathsProvider.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/IRemoteAuthenticationPathsProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..2294d7af225eb55045a0e8c2eb98006bf784faf6 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/IRemoteAuthenticationPathsProvider.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal +{ + /// <summary> + /// This is an internal API that supports the Microsoft.AspNetCore.Components.WebAssembly.Authentication + /// infrastructure and not subject to the same compatibility standards as public APIs. + /// It may be changed or removed without notice in any release. + /// </summary> + internal interface IRemoteAuthenticationPathsProvider + { + /// <summary> + /// This is an internal API that supports the Microsoft.AspNetCore.Components.WebAssembly.Authentication + /// infrastructure and not subject to the same compatibility standards as public APIs. + /// It may be changed or removed without notice in any release. + /// </summary> + RemoteAuthenticationApplicationPathsOptions ApplicationPaths { get; } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/AuthenticationService.ts b/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/AuthenticationService.ts new file mode 100644 index 0000000000000000000000000000000000000000..67b9bf6b6f97b14716bb5da19e4b434587fed2fe --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/AuthenticationService.ts @@ -0,0 +1,413 @@ +import { UserManager, UserManagerSettings, User } from 'oidc-client' + +type Writeable<T> = { -readonly [P in keyof T]: T[P] }; + +type ExtendedUserManagerSettings = Writeable<UserManagerSettings & AuthorizeServiceSettings> + +type OidcAuthorizeServiceSettings = ExtendedUserManagerSettings | ApiAuthorizationSettings; + +function isApiAuthorizationSettings(settings: OidcAuthorizeServiceSettings): settings is ApiAuthorizationSettings { + return settings.hasOwnProperty('configurationEndpoint'); +} + +interface AuthorizeServiceSettings { + defaultScopes: string[]; +} + +interface ApiAuthorizationSettings { + configurationEndpoint: string; +} + +export interface AccessTokenRequestOptions { + scopes: string[]; + returnUrl: string; +} + +export interface AccessTokenResult { + status: AccessTokenResultStatus; + token?: AccessToken; +} + +export interface AccessToken { + value: string; + expires: Date; + grantedScopes: string[]; +} + +export enum AccessTokenResultStatus { + Success = 'success', + RequiresRedirect = 'requiresRedirect' +} + +export enum AuthenticationResultStatus { + Redirect = 'redirect', + Success = 'success', + Failure = 'failure', + OperationCompleted = 'operationCompleted' +}; + +export interface AuthenticationResult { + status: AuthenticationResultStatus; + state?: unknown; + message?: string; +} + +export interface AuthorizeService { + getUser(): Promise<unknown>; + getAccessToken(request?: AccessTokenRequestOptions): Promise<AccessTokenResult>; + signIn(state: unknown): Promise<AuthenticationResult>; + completeSignIn(state: unknown): Promise<AuthenticationResult>; + signOut(state: unknown): Promise<AuthenticationResult>; + completeSignOut(url: string): Promise<AuthenticationResult>; +} + +class OidcAuthorizeService implements AuthorizeService { + private _userManager: UserManager; + private _intialSilentSignIn: Promise<void> | undefined; + constructor(userManager: UserManager) { + this._userManager = userManager; + } + + async trySilentSignIn() { + if (!this._intialSilentSignIn) { + this._intialSilentSignIn = (async () => { + try { + await this._userManager.signinSilent(); + } catch (e) { + // It is ok to swallow the exception here. + // The user might not be logged in and in that case it + // is expected for signinSilent to fail and throw + } + })(); + } + + return this._intialSilentSignIn; + } + + async getUser() { + if (window.parent === window && !window.opener && !window.frameElement && this._userManager.settings.redirect_uri && + !location.href.startsWith(this._userManager.settings.redirect_uri)) { + // If we are not inside a hidden iframe, try authenticating silently. + await AuthenticationService.instance.trySilentSignIn(); + } + + const user = await this._userManager.getUser(); + return user && user.profile; + } + + async getAccessToken(request?: AccessTokenRequestOptions): Promise<AccessTokenResult> { + const user = await this._userManager.getUser(); + if (hasValidAccessToken(user) && hasAllScopes(request, user.scopes)) { + return { + status: AccessTokenResultStatus.Success, + token: { + grantedScopes: user.scopes, + expires: getExpiration(user.expires_in), + value: user.access_token + } + }; + } else { + try { + const parameters = request && request.scopes ? + { scope: request.scopes.join(' ') } : undefined; + + const newUser = await this._userManager.signinSilent(parameters); + + return { + status: AccessTokenResultStatus.Success, + token: { + grantedScopes: newUser.scopes, + expires: getExpiration(newUser.expires_in), + value: newUser.access_token + } + }; + + } catch (e) { + return { + status: AccessTokenResultStatus.RequiresRedirect + }; + } + } + + function hasValidAccessToken(user: User | null): user is User { + return !!(user && user.access_token && !user.expired && user.scopes); + } + + function getExpiration(expiresIn: number) { + const now = new Date(); + now.setTime(now.getTime() + expiresIn * 1000); + return now; + } + + function hasAllScopes(request: AccessTokenRequestOptions | undefined, currentScopes: string[]) { + const set = new Set(currentScopes); + if (request && request.scopes) { + for (const current of request.scopes) { + if (!set.has(current)) { + return false; + } + } + } + + return true; + } + } + + async signIn(state: unknown) { + try { + await this._userManager.clearStaleState(); + await this._userManager.signinSilent(this.createArguments()); + return this.success(state); + } catch (silentError) { + try { + await this._userManager.clearStaleState(); + await this._userManager.signinRedirect(this.createArguments(state)); + return this.redirect(); + } catch (redirectError) { + return this.error(this.getExceptionMessage(redirectError)); + } + } + } + + async completeSignIn(url: string) { + const requiresLogin = await this.loginRequired(url); + const stateExists = await this.stateExists(url); + try { + const user = await this._userManager.signinCallback(url); + if (window.self !== window.top) { + return this.operationCompleted(); + } else { + return this.success(user && user.state); + } + } catch (error) { + if (requiresLogin || window.self !== window.top || !stateExists) { + return this.operationCompleted(); + } + + return this.error('There was an error signing in.'); + } + } + + async signOut(state: unknown) { + try { + if (!(await this._userManager.metadataService.getEndSessionEndpoint())) { + await this._userManager.removeUser(); + return this.success(state); + } + await this._userManager.signoutRedirect(this.createArguments(state)); + return this.redirect(); + } catch (redirectSignOutError) { + return this.error(this.getExceptionMessage(redirectSignOutError)); + } + } + + async completeSignOut(url: string) { + try { + if (await this.stateExists(url)) { + const response = await this._userManager.signoutCallback(url); + return this.success(response && response.state); + } else { + return this.operationCompleted(); + } + } catch (error) { + return this.error(this.getExceptionMessage(error)); + } + } + + private getExceptionMessage(error: any) { + if (isOidcError(error)) { + return error.error_description; + } else if (isRegularError(error)) { + return error.message; + } else { + return error.toString(); + } + + function isOidcError(error: any): error is (Oidc.SigninResponse & Oidc.SignoutResponse) { + return error && error.error_description; + } + + function isRegularError(error: any): error is Error { + return error && error.message; + } + } + + private async stateExists(url: string) { + const stateParam = new URLSearchParams(new URL(url).search).get('state'); + if (stateParam && this._userManager.settings.stateStore) { + return await this._userManager.settings.stateStore.get(stateParam); + } else { + return undefined; + } + } + + private async loginRequired(url: string) { + const errorParameter = new URLSearchParams(new URL(url).search).get('error'); + if (errorParameter && this._userManager.settings.stateStore) { + const error = await this._userManager.settings.stateStore.get(errorParameter); + return error === 'login_required'; + } else { + return false; + } + } + + private createArguments(state?: unknown) { + return { useReplaceToNavigate: true, data: state }; + } + + private error(message: string) { + return { status: AuthenticationResultStatus.Failure, errorMessage: message }; + } + + private success(state: unknown) { + return { status: AuthenticationResultStatus.Success, state }; + } + + private redirect() { + return { status: AuthenticationResultStatus.Redirect }; + } + + private operationCompleted() { + return { status: AuthenticationResultStatus.OperationCompleted }; + } +} + +export class AuthenticationService { + + static _infrastructureKey = 'Microsoft.AspNetCore.Components.WebAssembly.Authentication'; + static _initialized: Promise<void>; + static instance: OidcAuthorizeService; + static _pendingOperations: { [key: string]: Promise<AuthenticationResult> | undefined } = {} + + public static init(settings: UserManagerSettings & AuthorizeServiceSettings) { + // Multiple initializations can start concurrently and we want to avoid that. + // In order to do so, we create an initialization promise and the first call to init + // tries to initialize the app and sets up a promise other calls can await on. + if (!AuthenticationService._initialized) { + AuthenticationService._initialized = AuthenticationService.initializeCore(settings); + } + + return AuthenticationService._initialized; + } + + public static handleCallback() { + return AuthenticationService.initializeCore(); + } + + private static async initializeCore(settings?: UserManagerSettings & AuthorizeServiceSettings) { + const finalSettings = settings || AuthenticationService.resolveCachedSettings(); + if (!settings && finalSettings) { + const userManager = AuthenticationService.createUserManagerCore(finalSettings); + + if (window.parent !== window && !window.opener && (window.frameElement && userManager.settings.redirect_uri && + location.href.startsWith(userManager.settings.redirect_uri))) { + // If we are inside a hidden iframe, try completing the sign in early. + // This prevents loading the blazor app inside a hidden iframe, which speeds up the authentication operations + // and avoids wasting resources (CPU and memory from bootstrapping the Blazor app) + AuthenticationService.instance = new OidcAuthorizeService(userManager); + + // This makes sure that if the blazor app has time to load inside the hidden iframe, + // it is not able to perform another auth operation until this operation has completed. + AuthenticationService._initialized = (async (): Promise<void> => { + await AuthenticationService.instance.completeSignIn(location.href); + return; + })(); + } + } else if (settings) { + const userManager = await AuthenticationService.createUserManager(settings); + AuthenticationService.instance = new OidcAuthorizeService(userManager); + } else { + // HandleCallback gets called unconditionally, so we do nothing for normal paths. + // Cached settings are only used on handling the redirect_uri path and if the settings are not there + // the app will fallback to the default logic for handling the redirect. + } + } + + private static resolveCachedSettings(): UserManagerSettings | undefined { + const cachedSettings = window.sessionStorage.getItem(`${AuthenticationService._infrastructureKey}.CachedAuthSettings`); + return cachedSettings ? JSON.parse(cachedSettings) : undefined; + } + + public static getUser() { + return AuthenticationService.instance.getUser(); + } + + public static getAccessToken(options: AccessTokenRequestOptions) { + return AuthenticationService.instance.getAccessToken(options); + } + + public static signIn(state: unknown) { + return AuthenticationService.instance.signIn(state); + } + + public static async completeSignIn(url: string) { + let operation = this._pendingOperations[url]; + if (!operation) { + operation = AuthenticationService.instance.completeSignIn(url); + await operation; + delete this._pendingOperations[url]; + } + + return operation; + } + + public static signOut(state: unknown) { + return AuthenticationService.instance.signOut(state); + } + + public static async completeSignOut(url: string) { + let operation = this._pendingOperations[url]; + if (!operation) { + operation = AuthenticationService.instance.completeSignOut(url); + await operation; + delete this._pendingOperations[url]; + } + + return operation; + } + + private static async createUserManager(settings: OidcAuthorizeServiceSettings): Promise<UserManager> { + let finalSettings: UserManagerSettings; + if (isApiAuthorizationSettings(settings)) { + const response = await fetch(settings.configurationEndpoint); + if (!response.ok) { + throw new Error(`Could not load settings from '${settings.configurationEndpoint}'`); + } + + const downloadedSettings = await response.json(); + + finalSettings = downloadedSettings; + } else { + if (!settings.scope) { + settings.scope = settings.defaultScopes.join(' '); + } + + if (settings.response_type === null) { + // If the response type is not set, it gets serialized as null. OIDC-client behaves differently than when the value is undefined, so we explicitly check for a null value and remove the property instead. + delete settings.response_type; + } + + finalSettings = settings; + } + + window.sessionStorage.setItem(`${AuthenticationService._infrastructureKey}.CachedAuthSettings`, JSON.stringify(finalSettings)); + + return AuthenticationService.createUserManagerCore(finalSettings); + } + + private static createUserManagerCore(finalSettings: UserManagerSettings) { + const userManager = new UserManager(finalSettings); + userManager.events.addUserSignedOut(async () => { + userManager.removeUser(); + }); + return userManager; + } +} + +declare global { + interface Window { AuthenticationService: AuthenticationService } +} + +AuthenticationService.handleCallback(); + +window.AuthenticationService = AuthenticationService; diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/dist/.gitignore b/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/dist/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4e57eef8845b8583ecaec86fc7f4e8bd0eed5ac9 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/dist/.gitignore @@ -0,0 +1,2 @@ +*.js +*.js.map diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/package.json b/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/package.json new file mode 100644 index 0000000000000000000000000000000000000000..da5588d519ce1db4c4cab9a8fa470ba8af91c93f --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/package.json @@ -0,0 +1,18 @@ +{ + "private": true, + "scripts": { + "build": "npm run build:release", + "build:release": "webpack --mode production --env.production --env.configuration=Release", + "build:debug": "webpack --mode development --env.configuration=Debug", + "watch": "webpack --watch --mode development --env.configuration=Debug" + }, + "devDependencies": { + "ts-loader": "^6.2.1", + "typescript": "^3.7.5", + "webpack": "^4.41.5", + "webpack-cli": "^3.3.10" + }, + "dependencies": { + "oidc-client": "^1.10.1" + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/tsconfig.json b/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..13316f2a9e8557aa6d24c87f8ce38ca39a2c5c97 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES2019", + "module": "commonjs", + "lib": [ "DOM", "ES2019" ], + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/webpack.config.js b/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/webpack.config.js new file mode 100644 index 0000000000000000000000000000000000000000..d3052a790892413e3e4e79305af5238d23ce2beb --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/webpack.config.js @@ -0,0 +1,25 @@ +const path = require('path'); + +module.exports = env => { + + return { + entry: './AuthenticationService.ts', + devtool: env && env.production ? 'none' : 'source-map', + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'], + }, + output: { + filename: 'AuthenticationService.js', + path: path.resolve(__dirname, 'dist', env.configuration), + }, + }; +}; diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/yarn.lock b/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/yarn.lock new file mode 100644 index 0000000000000000000000000000000000000000..f2c61f29410efed628a7951d785530e973ba95f6 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Interop/yarn.lock @@ -0,0 +1,2737 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@webassemblyjs/ast@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" + integrity sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ== + dependencies: + "@webassemblyjs/helper-module-context" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/wast-parser" "1.8.5" + +"@webassemblyjs/floating-point-hex-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz#1ba926a2923613edce496fd5b02e8ce8a5f49721" + integrity sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ== + +"@webassemblyjs/helper-api-error@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz#c49dad22f645227c5edb610bdb9697f1aab721f7" + integrity sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA== + +"@webassemblyjs/helper-buffer@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz#fea93e429863dd5e4338555f42292385a653f204" + integrity sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q== + +"@webassemblyjs/helper-code-frame@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz#9a740ff48e3faa3022b1dff54423df9aa293c25e" + integrity sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ== + dependencies: + "@webassemblyjs/wast-printer" "1.8.5" + +"@webassemblyjs/helper-fsm@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz#ba0b7d3b3f7e4733da6059c9332275d860702452" + integrity sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow== + +"@webassemblyjs/helper-module-context@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz#def4b9927b0101dc8cbbd8d1edb5b7b9c82eb245" + integrity sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g== + dependencies: + "@webassemblyjs/ast" "1.8.5" + mamacro "^0.0.3" + +"@webassemblyjs/helper-wasm-bytecode@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz#537a750eddf5c1e932f3744206551c91c1b93e61" + integrity sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ== + +"@webassemblyjs/helper-wasm-section@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz#74ca6a6bcbe19e50a3b6b462847e69503e6bfcbf" + integrity sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + +"@webassemblyjs/ieee754@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz#712329dbef240f36bf57bd2f7b8fb9bf4154421e" + integrity sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.8.5.tgz#044edeb34ea679f3e04cd4fd9824d5e35767ae10" + integrity sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.8.5.tgz#a8bf3b5d8ffe986c7c1e373ccbdc2a0915f0cedc" + integrity sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw== + +"@webassemblyjs/wasm-edit@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz#962da12aa5acc1c131c81c4232991c82ce56e01a" + integrity sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/helper-wasm-section" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + "@webassemblyjs/wasm-opt" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + "@webassemblyjs/wast-printer" "1.8.5" + +"@webassemblyjs/wasm-gen@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz#54840766c2c1002eb64ed1abe720aded714f98bc" + integrity sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/ieee754" "1.8.5" + "@webassemblyjs/leb128" "1.8.5" + "@webassemblyjs/utf8" "1.8.5" + +"@webassemblyjs/wasm-opt@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz#b24d9f6ba50394af1349f510afa8ffcb8a63d264" + integrity sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + +"@webassemblyjs/wasm-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz#21576f0ec88b91427357b8536383668ef7c66b8d" + integrity sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-api-error" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/ieee754" "1.8.5" + "@webassemblyjs/leb128" "1.8.5" + "@webassemblyjs/utf8" "1.8.5" + +"@webassemblyjs/wast-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz#e10eecd542d0e7bd394f6827c49f3df6d4eefb8c" + integrity sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/floating-point-hex-parser" "1.8.5" + "@webassemblyjs/helper-api-error" "1.8.5" + "@webassemblyjs/helper-code-frame" "1.8.5" + "@webassemblyjs/helper-fsm" "1.8.5" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/wast-printer@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz#114bbc481fd10ca0e23b3560fa812748b0bae5bc" + integrity sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/wast-parser" "1.8.5" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +acorn@^6.2.1: + version "6.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.0.tgz#b659d2ffbafa24baf5db1cdbb2c94a983ecd2784" + integrity sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw== + +ajv-errors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" + integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== + +ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" + integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== + +ajv@^6.1.0, ajv@^6.10.2: + version "6.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9" + integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +aproba@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +asn1.js@^4.0.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +assert@^1.1.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== + dependencies: + object-assign "^4.1.1" + util "0.10.3" + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +async-each@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@^1.0.2, base64-js@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" + integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^1.0.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bluebird@^3.5.5: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: + version "4.11.8" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1, braces@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +brorand@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= + +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= + dependencies: + bn.js "^4.1.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" + integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= + dependencies: + bn.js "^4.1.1" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.2" + elliptic "^6.0.0" + inherits "^2.0.1" + parse-asn1 "^5.0.0" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + dependencies: + pako "~1.0.5" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= + +buffer@^4.3.0: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= + +cacache@^12.0.2: + version "12.0.3" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390" + integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw== + dependencies: + bluebird "^3.5.5" + chownr "^1.1.1" + figgy-pudding "^3.5.1" + glob "^7.1.4" + graceful-fs "^4.1.15" + infer-owner "^1.0.3" + lru-cache "^5.1.1" + mississippi "^3.0.0" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + promise-inflight "^1.0.1" + rimraf "^2.6.3" + ssri "^6.0.1" + unique-filename "^1.1.1" + y18n "^4.0.0" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +chalk@2.4.2, chalk@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chokidar@^2.0.2: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.1" + optionalDependencies: + fsevents "^1.2.7" + +chownr@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" + integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== + +chrome-trace-event@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" + integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== + dependencies: + tslib "^1.9.0" + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@^1.5.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +console-browserify@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== + +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= + +copy-concurrently@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" + integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== + dependencies: + aproba "^1.1.1" + fs-write-stream-atomic "^1.0.8" + iferr "^0.1.5" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.0" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-js@^2.6.4: + version "2.6.11" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" + integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +create-ecdh@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" + integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== + dependencies: + bn.js "^4.1.0" + elliptic "^6.0.0" + +create-hash@^1.1.0, create-hash@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cross-spawn@6.0.5, cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +crypto-browserify@^3.11.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + +crypto-js@^3.1.9-1: + version "3.1.9-1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.9-1.tgz#fda19e761fc077e01ffbfdc6e9fdfc59e8806cd8" + integrity sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg= + +cyclist@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" + integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= + +debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +des.js@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +detect-file@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" + integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= + +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +domain-browser@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== + +duplexify@^3.4.2, duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +elliptic@^6.0.0: + version "6.5.2" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" + integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emojis-list@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" + integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enhanced-resolve@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" + integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.4.0" + tapable "^1.0.0" + +enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66" + integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + +errno@^0.1.3, errno@~0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" + integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== + dependencies: + prr "~1.0.1" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== + dependencies: + estraverse "^4.1.0" + +estraverse@^4.1.0, estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +events@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" + integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expand-tilde@^2.0.0, expand-tilde@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" + integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= + dependencies: + homedir-polyfill "^1.0.1" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +figgy-pudding@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" + integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-cache-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + dependencies: + commondir "^1.0.1" + make-dir "^2.0.0" + pkg-dir "^3.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +findup-sync@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" + integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + +flush-write-stream@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + dependencies: + inherits "^2.0.3" + readable-stream "^2.3.6" + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +from2@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" + integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" + +fs-write-stream-atomic@^1.0.8: + version "1.0.10" + resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" + integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= + dependencies: + graceful-fs "^4.1.2" + iferr "^0.1.5" + imurmurhash "^0.1.4" + readable-stream "1 || 2" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.2.7: + version "1.2.11" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.11.tgz#67bf57f4758f02ede88fb2a1712fef4d15358be3" + integrity sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob@^7.1.3, glob@^7.1.4: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-modules@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-modules@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" + integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== + dependencies: + global-prefix "^1.0.1" + is-windows "^1.0.1" + resolve-dir "^1.0.0" + +global-prefix@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" + integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= + dependencies: + expand-tilde "^2.0.2" + homedir-polyfill "^1.0.1" + ini "^1.3.4" + is-windows "^1.0.1" + which "^1.2.14" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +hash-base@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hmac-drbg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +homedir-polyfill@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + dependencies: + parse-passwd "^1.0.0" + +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= + +ieee754@^1.1.4: + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== + +iferr@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" + integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= + +import-local@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" + integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== + dependencies: + pkg-dir "^3.0.0" + resolve-cwd "^2.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +infer-owner@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@^1.3.4, ini@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +interpret@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" + integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== + +invert-kv@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" + integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +is-windows@^1.0.1, is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +lcid@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" + integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== + dependencies: + invert-kv "^2.0.0" + +loader-runner@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" + integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== + +loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" + integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== + dependencies: + big.js "^5.2.2" + emojis-list "^2.0.0" + json5 "^1.0.1" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +mamacro@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" + integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA== + +map-age-cleaner@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +mem@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" + integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== + dependencies: + map-age-cleaner "^0.1.1" + mimic-fn "^2.0.0" + p-is-promise "^2.0.0" + +memory-fs@^0.4.0, memory-fs@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +micromatch@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +mimic-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +mississippi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" + integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== + dependencies: + concat-stream "^1.5.0" + duplexify "^3.4.2" + end-of-stream "^1.1.0" + flush-write-stream "^1.0.0" + from2 "^2.1.0" + parallel-transform "^1.1.0" + pump "^3.0.0" + pumpify "^1.3.3" + stream-each "^1.1.0" + through2 "^2.0.0" + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +move-concurrently@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" + integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= + dependencies: + aproba "^1.1.1" + copy-concurrently "^1.0.0" + fs-write-stream-atomic "^1.0.8" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.3" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +nan@^2.12.1: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +neo-async@^2.5.0, neo-async@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" + integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-libs-browser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== + dependencies: + assert "^1.1.1" + browserify-zlib "^0.2.0" + buffer "^4.3.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "^3.11.0" + domain-browser "^1.1.1" + events "^3.0.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" + path-browserify "0.0.1" + process "^0.11.10" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.3.3" + stream-browserify "^2.0.1" + stream-http "^2.7.2" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.11.0" + vm-browserify "^1.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +oidc-client@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/oidc-client/-/oidc-client-1.10.1.tgz#fe67ae54924fc1c338062f3fd733be362026192c" + integrity sha512-/QB5Nl7c9GmT9ir1E+OVY3+yZZnuk7Qa9ZEAJqSvDq0bAyAU9KAgeKipTEfKjGdGLTeOLy9FRWuNpULMkfZydQ== + dependencies: + base64-js "^1.3.0" + core-js "^2.6.4" + crypto-js "^3.1.9-1" + uuid "^3.3.2" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= + +os-locale@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" + integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== + dependencies: + execa "^1.0.0" + lcid "^2.0.0" + mem "^4.0.0" + +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-is-promise@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" + integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== + +p-limit@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" + integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== + dependencies: + p-try "^2.0.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@~1.0.5: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +parallel-transform@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" + integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== + dependencies: + cyclist "^1.0.1" + inherits "^2.0.3" + readable-stream "^2.1.5" + +parse-asn1@^5.0.0: + version "5.1.5" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" + integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== + dependencies: + asn1.js "^4.0.0" + browserify-aes "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +pbkdf2@^3.0.3: + version "3.0.17" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" + integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +picomatch@^2.0.5: + version "2.2.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" + integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.3: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + dependencies: + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + +punycode@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +querystring-es3@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= + dependencies: + resolve-from "^3.0.0" + +resolve-dir@^1.0.0, resolve-dir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" + integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= + dependencies: + expand-tilde "^2.0.0" + global-modules "^1.0.0" + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +rimraf@^2.5.4, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +run-queue@^1.0.0, run-queue@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" + integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= + dependencies: + aproba "^1.1.1" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +schema-utils@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" + integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== + dependencies: + ajv "^6.1.0" + ajv-errors "^1.0.0" + ajv-keywords "^3.1.0" + +semver@^5.5.0, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +serialize-javascript@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" + integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-list-map@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@~0.5.12: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +ssri@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" + integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== + dependencies: + figgy-pudding "^3.5.1" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +stream-browserify@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" + integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + +stream-each@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" + integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== + dependencies: + end-of-stream "^1.1.0" + stream-shift "^1.0.0" + +stream-http@^2.7.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.3.6" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string_decoder@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + +supports-color@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +tapable@^1.0.0, tapable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +terser-webpack-plugin@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" + integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA== + dependencies: + cacache "^12.0.2" + find-cache-dir "^2.1.0" + is-wsl "^1.1.0" + schema-utils "^1.0.0" + serialize-javascript "^2.1.2" + source-map "^0.6.1" + terser "^4.1.2" + webpack-sources "^1.4.0" + worker-farm "^1.7.0" + +terser@^4.1.2: + version "4.6.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.3.tgz#e33aa42461ced5238d352d2df2a67f21921f8d87" + integrity sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + +through2@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +timers-browserify@^2.0.4: + version "2.0.11" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" + integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== + dependencies: + setimmediate "^1.0.4" + +to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +ts-loader@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.1.tgz#67939d5772e8a8c6bdaf6277ca023a4812da02ef" + integrity sha512-Dd9FekWuABGgjE1g0TlQJ+4dFUfYGbYcs52/HQObE0ZmUNjQlmLAS7xXsSzy23AMaMwipsx5sNHvoEpT2CZq1g== + dependencies: + chalk "^2.3.0" + enhanced-resolve "^4.0.0" + loader-utils "^1.0.2" + micromatch "^4.0.0" + semver "^6.0.0" + +tslib@^1.9.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + +tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +typescript@^3.7.5: + version "3.7.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" + integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= + dependencies: + inherits "2.0.1" + +util@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" + integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== + dependencies: + inherits "2.0.3" + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +v8-compile-cache@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" + integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== + +vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== + +watchpack@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" + integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== + dependencies: + chokidar "^2.0.2" + graceful-fs "^4.1.2" + neo-async "^2.5.0" + +webpack-cli@^3.3.10: + version "3.3.10" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.10.tgz#17b279267e9b4fb549023fae170da8e6e766da13" + integrity sha512-u1dgND9+MXaEt74sJR4PR7qkPxXUSQ0RXYq8x1L6Jg1MYVEmGPrH6Ah6C4arD4r0J1P5HKjRqpab36k0eIzPqg== + dependencies: + chalk "2.4.2" + cross-spawn "6.0.5" + enhanced-resolve "4.1.0" + findup-sync "3.0.0" + global-modules "2.0.0" + import-local "2.0.0" + interpret "1.2.0" + loader-utils "1.2.3" + supports-color "6.1.0" + v8-compile-cache "2.0.3" + yargs "13.2.4" + +webpack-sources@^1.4.0, webpack-sources@^1.4.1: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== + dependencies: + source-list-map "^2.0.0" + source-map "~0.6.1" + +webpack@^4.41.5: + version "4.41.5" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.5.tgz#3210f1886bce5310e62bb97204d18c263341b77c" + integrity sha512-wp0Co4vpyumnp3KlkmpM5LWuzvZYayDwM2n17EHFr4qxBBbRokC7DJawPJC7TfSFZ9HZ6GsdH40EBj4UV0nmpw== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-module-context" "1.8.5" + "@webassemblyjs/wasm-edit" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + acorn "^6.2.1" + ajv "^6.10.2" + ajv-keywords "^3.4.1" + chrome-trace-event "^1.0.2" + enhanced-resolve "^4.1.0" + eslint-scope "^4.0.3" + json-parse-better-errors "^1.0.2" + loader-runner "^2.4.0" + loader-utils "^1.2.3" + memory-fs "^0.4.1" + micromatch "^3.1.10" + mkdirp "^0.5.1" + neo-async "^2.6.1" + node-libs-browser "^2.2.1" + schema-utils "^1.0.0" + tapable "^1.1.3" + terser-webpack-plugin "^1.4.3" + watchpack "^1.6.0" + webpack-sources "^1.4.1" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@^1.2.14, which@^1.2.9, which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +worker-farm@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" + integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== + dependencies: + errno "~0.1.7" + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +xtend@^4.0.0, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^13.1.0: + version "13.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" + integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@13.2.4: + version "13.2.4" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" + integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + os-locale "^3.1.0" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.0" diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj b/src/Components/WebAssembly/WebAssembly.Authentication/src/Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj new file mode 100644 index 0000000000000000000000000000000000000000..d0be43efcdac95364cc70487a9fcd08c4a71f64b --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj @@ -0,0 +1,84 @@ +<Project Sdk="Microsoft.NET.Sdk.Razor"> + + <Sdk Name="Yarn.MSBuild" /> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + <Description>Build client-side authentication for single-page applications (SPAs).</Description> + <IsShippingPackage>true</IsShippingPackage> + <HasReferenceAssembly>false</HasReferenceAssembly> + <RazorLangVersion>3.0</RazorLangVersion> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + </PropertyGroup> + + <ItemGroup> + <Reference Include="Microsoft.AspNetCore.Components.Authorization" /> + <Reference Include="Microsoft.AspNetCore.Components.Web" /> + </ItemGroup> + + <ItemGroup> + <InternalsVisibleTo Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests" /> + </ItemGroup> + + <PropertyGroup> + <YarnWorkingDir>$(MSBuildThisFileDirectory)Interop\</YarnWorkingDir> + <ResolveCurrentProjectStaticWebAssetsInputsDependsOn> + CompileInterop; + $(ResolveCurrentProjectStaticWebAssetsInputsDependsOn) + </ResolveCurrentProjectStaticWebAssetsInputsDependsOn> + </PropertyGroup> + + <ItemGroup> + <YarnInputs Include="$(YarnWorkingDir)**" Exclude="$(YarnWorkingDir)node_modules\**;$(YarnWorkingDir)*.d.ts;$(YarnWorkingDir)dist\**" /> + <YarnOutputs Include="$(YarnWorkingDir)dist\$(Configuration)\AuthenticationService.js" /> + <YarnOutputs Include="$(YarnWorkingDir)dist\$(Configuration)\AuthenticationService.js.map" Condition="'$(Configuration)' == 'Debug'" /> + + <Content Remove="$(YarnWorkingDir)**" /> + <None Include="$(YarnWorkingDir)*" Exclude="$(YarnWorkingDir)node_modules\**" /> + + <UpToDateCheckInput Include="@(YarnInputs)" Set="StaticWebassets" /> + <UpToDateCheckOutput Include="@(YarnOutputs)" Set="StaticWebassets" /> + </ItemGroup> + + <Target Name="_CreateInteropHash" BeforeTargets="CompileInterop" Condition="'$(DesignTimeBuild)' != 'true'"> + + <PropertyGroup> + <InteropCompilationCacheFile>$(IntermediateOutputPath)interop.cache</InteropCompilationCacheFile> + </PropertyGroup> + + <Hash ItemsToHash="@(YarnInputs)"> + <Output TaskParameter="HashResult" PropertyName="_YarnInputsHash" /> + </Hash> + + <WriteLinesToFile Lines="$(_YarnInputsHash)" File="$(InteropCompilationCacheFile)" Overwrite="True" WriteOnlyWhenDifferent="True" /> + + <ItemGroup> + <FileWrites Include="$(InteropCompilationCacheFile)" /> + </ItemGroup> + + </Target> + + <Target Name="CompileInterop" Condition="'$(DesignTimeBuild)' != 'true'" Inputs="$(InteropCompilationCacheFile)" Outputs="@(YarnOutputs)"> + <Yarn Command="install --mutex network" WorkingDirectory="$(YarnWorkingDir)" /> + <Yarn Command="run build:release" WorkingDirectory="$(YarnWorkingDir)" Condition="'$(Configuration)' == 'Release'" /> + <Yarn Command="run build:debug" WorkingDirectory="$(YarnWorkingDir)" Condition="'$(Configuration)' == 'Debug'" /> + + <ItemGroup> + <_InteropBuildOutput Include="$(YarnWorkingDir)dist\$(Configuration)\**" Exclude="$(YarnWorkingDir)dist\.gitignore" /> + + <StaticWebAsset Include="@(_InteropBuildOutput->'%(FullPath)')"> + <SourceType></SourceType> + <SourceId>$(PackageId)</SourceId> + <ContentRoot>$([MSBuild]::NormalizeDirectory('$(YarnWorkingDir)\dist\$(Configuration)'))</ContentRoot> + <BasePath>_content/$(PackageId)</BasePath> + <RelativePath>$([System.String]::Copy('%(RecursiveDir)%(FileName)%(Extension)').Replace('\','/'))</RelativePath> + </StaticWebAsset> + + <FileWrites Include="$(_InteropBuildOutput)" /> + </ItemGroup> + + <Message Importance="high" Text="@(_InteropBuildOutput->'Emitted %(FullPath)')" /> + + </Target> + +</Project> diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/AccessToken.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/AccessToken.cs new file mode 100644 index 0000000000000000000000000000000000000000..a282d1c79470f9308f9eaa0e840b7de5e40ef775 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/AccessToken.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents an access token for a given user and scopes. + /// </summary> + public class AccessToken + { + /// <summary> + /// Gets or sets the list of granted scopes for the token. + /// </summary> + public IReadOnlyList<string> GrantedScopes { get; set; } + + /// <summary> + /// Gets the expiration time of the token. + /// </summary> + public DateTimeOffset Expires { get; set; } + + /// <summary> + /// Gets the serialized representation of the token. + /// </summary> + public string Value { get; set; } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationActions.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationActions.cs new file mode 100644 index 0000000000000000000000000000000000000000..fda3b5efbc80ae1d975ccd30c5cfed73c4a98573 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationActions.cs @@ -0,0 +1,64 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents the list of authentication actions that can be performed by the <see cref="RemoteAuthenticatorViewCore{TAuthenticationState}"/>. + /// </summary> + public class RemoteAuthenticationActions + { + /// <summary> + /// The log in action. + /// </summary> + public const string LogIn = "login"; + + /// <summary> + /// The log in callback action. + /// </summary> + public const string LogInCallback = "login-callback"; + + /// <summary> + /// The log in failed action. + /// </summary> + public const string LogInFailed = "login-failed"; + + /// <summary> + /// The navigate to user profile action. + /// </summary> + public const string Profile = "profile"; + + /// <summary> + /// The navigate to register action. + /// </summary> + public const string Register = "register"; + + /// <summary> + /// The log out action. + /// </summary> + public const string LogOut = "logout"; + + /// <summary> + /// The log out callback action. + /// </summary> + public const string LogOutCallback = "logout-callback"; + + /// <summary> + /// The log out failed action. + /// </summary> + public const string LogOutFailed = "logout-failed"; + + /// <summary> + /// The log out succeeded action. + /// </summary> + public const string LogOutSucceeded = "logged-out"; + + /// <summary> + /// Whether or not a given <paramref name="candidate"/> represents a given <see cref="RemoteAuthenticationActions"/>. + /// </summary> + /// <param name="action">The <see cref="RemoteAuthenticationActions"/>.</param> + /// <param name="candidate">The candidate.</param> + /// <returns>Whether or not is the given <see cref="RemoteAuthenticationActions"/> action.</returns> + public static bool IsAction(string action, string candidate) => action != null && string.Equals(action, candidate, System.StringComparison.OrdinalIgnoreCase); + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationContext.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationContext.cs new file mode 100644 index 0000000000000000000000000000000000000000..f54a69c558a02c58582657803ba98e869374b112 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationContext.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents the context during authentication operations. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState"></typeparam> + public class RemoteAuthenticationContext<TRemoteAuthenticationState> where TRemoteAuthenticationState : RemoteAuthenticationState + { + /// <summary> + /// Gets or sets the url for the current authentication operation. + /// </summary> + public string Url { get; set; } + + /// <summary> + /// Gets or sets the state instance for the current authentication operation. + /// </summary> + public TRemoteAuthenticationState State { get; set; } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationResult.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationResult.cs new file mode 100644 index 0000000000000000000000000000000000000000..529f34e54b0125b0c077b234ca77124019746c58 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationResult.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents the result of an authentication operation. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The type of the preserved state during the authentication operation.</typeparam> + public class RemoteAuthenticationResult<TRemoteAuthenticationState> where TRemoteAuthenticationState : RemoteAuthenticationState + { + /// <summary> + /// Gets or sets the status of the authentication operation. The status can be one of <see cref="RemoteAuthenticationStatus"/>. + /// </summary> + public RemoteAuthenticationStatus Status { get; set; } + + /// <summary> + /// Gets or sets the error message of a failed authentication operation. + /// </summary> + public string ErrorMessage { get; set; } + + /// <summary> + /// Gets or sets the preserved state of a successful authentication operation. + /// </summary> + public TRemoteAuthenticationState State { get; set; } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationState.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationState.cs new file mode 100644 index 0000000000000000000000000000000000000000..461a0d83dda48c3cc9c546bf2154b75152d5968e --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationState.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents the minimal amount of authentication state to be preserved during authentication operations. + /// </summary> + public class RemoteAuthenticationState + { + /// <summary> + /// Gets or sets the URL to which the application should redirect after a successful authentication operation. + /// It must be a url within the page. + /// </summary> + public string ReturnUrl { get; set; } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationStatus.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationStatus.cs new file mode 100644 index 0000000000000000000000000000000000000000..08cf1a69a0790dfeca506858bf2b3f881569640e --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteAuthenticationStatus.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents the status of an authentication operation. + /// </summary> + public enum RemoteAuthenticationStatus + { + /// <summary> + /// The application is going to be redirected. + /// </summary> + Redirect, + + /// <summary> + /// The authentication operation completed successfully. + /// </summary> + Success, + + /// <summary> + /// There was an error performing the authentication operation. + /// </summary> + Failure, + + /// <summary> + /// The operation in the current navigation context has completed. This signals that the application running on the + /// current browser context is about to be shut down and no other work is required. + /// </summary> + OperationCompleted, + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteUserAccount.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteUserAccount.cs new file mode 100644 index 0000000000000000000000000000000000000000..0218aebce3b2ca5953b8ac7e3230c1ccf0ef1914 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Models/RemoteUserAccount.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// A user account. + /// </summary> + /// <remarks> + /// The information in this type will be use to produce a <see cref="System.Security.Claims.ClaimsPrincipal"/> for the application. + /// </remarks> + public class RemoteUserAccount + { + /// <summary> + /// Gets or sets properties not explicitly mapped about the user. + /// </summary> + [JsonExtensionData] + public IDictionary<string, object> AdditionalProperties { get; set; } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/ApiAuthorizationProviderOptions.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/ApiAuthorizationProviderOptions.cs new file mode 100644 index 0000000000000000000000000000000000000000..3c51793a0484b83443f17f2e7584369a4e7ba15f --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/ApiAuthorizationProviderOptions.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents options for applications relying on a server for configuration. + /// </summary> + public class ApiAuthorizationProviderOptions + { + /// <summary> + /// Gets or sets the endpoint to call to retrieve the authentication settings for the application. + /// </summary> + public string ConfigurationEndpoint { get; set; } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/DefaultApiAuthorizationOptionsConfiguration.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/DefaultApiAuthorizationOptionsConfiguration.cs new file mode 100644 index 0000000000000000000000000000000000000000..a1cd533c1e7bb34b2e5dd50d63a55725b7e6d4f2 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/DefaultApiAuthorizationOptionsConfiguration.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + internal class DefaultApiAuthorizationOptionsConfiguration : IPostConfigureOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> + { + private readonly string _applicationName; + + public DefaultApiAuthorizationOptionsConfiguration(string applicationName) => _applicationName = applicationName; + + public void Configure(RemoteAuthenticationOptions<ApiAuthorizationProviderOptions> options) + { + options.ProviderOptions.ConfigurationEndpoint ??= $"_configuration/{_applicationName}"; + options.AuthenticationPaths.RemoteRegisterPath ??= "Identity/Account/Register"; + options.AuthenticationPaths.RemoteProfilePath ??= "Identity/Account/Manage"; + options.UserOptions.ScopeClaim ??= "scope"; + options.UserOptions.RoleClaim ??= "role"; + options.UserOptions.AuthenticationType ??= _applicationName; + } + + public void PostConfigure(string name, RemoteAuthenticationOptions<ApiAuthorizationProviderOptions> options) + { + if (string.Equals(name, Options.DefaultName)) + { + Configure(options); + } + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/DefaultOidcProviderOptionsConfiguration.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/DefaultOidcProviderOptionsConfiguration.cs new file mode 100644 index 0000000000000000000000000000000000000000..886d7ddcbbf19ef77384d34796dce80b63f11b74 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/DefaultOidcProviderOptionsConfiguration.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + internal class DefaultOidcOptionsConfiguration : IPostConfigureOptions<RemoteAuthenticationOptions<OidcProviderOptions>> + { + private readonly NavigationManager _navigationManager; + + public DefaultOidcOptionsConfiguration(NavigationManager navigationManager) => _navigationManager = navigationManager; + + public void Configure(RemoteAuthenticationOptions<OidcProviderOptions> options) + { + options.UserOptions.AuthenticationType ??= options.ProviderOptions.ClientId; + + var redirectUri = options.ProviderOptions.RedirectUri; + if (redirectUri == null || !Uri.TryCreate(redirectUri, UriKind.Absolute, out _)) + { + redirectUri ??= "authentication/login-callback"; + options.ProviderOptions.RedirectUri = _navigationManager + .ToAbsoluteUri(redirectUri).AbsoluteUri; + } + + var logoutUri = options.ProviderOptions.PostLogoutRedirectUri; + if (logoutUri == null || !Uri.TryCreate(logoutUri, UriKind.Absolute, out _)) + { + logoutUri ??= "authentication/logout-callback"; + options.ProviderOptions.PostLogoutRedirectUri = _navigationManager + .ToAbsoluteUri(logoutUri).AbsoluteUri; + } + } + + public void PostConfigure(string name, RemoteAuthenticationOptions<OidcProviderOptions> options) + { + if (string.Equals(name, Options.DefaultName)) + { + Configure(options); + } + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/OidcProviderOptions.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/OidcProviderOptions.cs new file mode 100644 index 0000000000000000000000000000000000000000..dbb43e1dbf14d3cf65901d934f0a46ae21a165d0 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/OidcProviderOptions.cs @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents options to pass down to configure the oidc-client.js library used when using a standard OIDC flow. + /// </summary> + public class OidcProviderOptions + { + /// <summary> + /// Gets or sets the authority of the OIDC identity provider. + /// </summary> + public string Authority { get; set; } + + /// <summary> + /// Gets or sets the metadata url of the oidc provider. + /// </summary> + public string MetadataUrl { get; set; } + + /// <summary> + /// Gets or sets the client of the application. + /// </summary> + [JsonPropertyName("client_id")] + public string ClientId { get; set; } + + /// <summary> + /// Gets or sets the list of scopes to request when signing in. + /// </summary> + public IList<string> DefaultScopes { get; } = new List<string> { "openid", "profile" }; + + /// <summary> + /// Gets or sets the redirect uri for the application. The application will be redirected here after the user has completed the sign in + /// process from the identity provider. + /// </summary> + [JsonPropertyName("redirect_uri")] + public string RedirectUri { get; set; } + + /// <summary> + /// Gets or sets the post logout redirect uri for the application. The application will be redirected here after the user has completed the sign out + /// process from the identity provider. + /// </summary> + [JsonPropertyName("post_logout_redirect_uri")] + public string PostLogoutRedirectUri { get; set; } + + /// <summary> + /// Gets or sets the response type to use on the authorization flow. The valid values are specified by the identity provider metadata. + /// </summary> + [JsonPropertyName("response_type")] + public string ResponseType { get; set; } + + /// <summary> + /// Gets or sets the response mode to use in the authorization flow. + /// </summary> + [JsonPropertyName("response_mode")] + public string ResponseMode { get; set; } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/RemoteAuthenticationApplicationPathsOptions.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/RemoteAuthenticationApplicationPathsOptions.cs new file mode 100644 index 0000000000000000000000000000000000000000..0a30feedf80cdc4eb793e48f1e3ec6b025fddeb9 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/RemoteAuthenticationApplicationPathsOptions.cs @@ -0,0 +1,68 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents the options for the paths used by the application for authentication operations. These paths are relative to the base. + /// </summary> + public class RemoteAuthenticationApplicationPathsOptions + { + /// <summary> + /// Gets or sets the path to the endpoint for registering new users. + /// </summary> + public string RegisterPath { get; set; } = RemoteAuthenticationDefaults.RegisterPath; + + /// <summary> + /// Gets or sets the remote path to the remote endpoint for registering new users. + /// It might be absolute and point outside of the application. + /// </summary> + public string RemoteRegisterPath { get; set; } + + /// <summary> + /// Gets or sets the path to the endpoint for modifying the settings for the user profile. + /// </summary> + public string ProfilePath { get; set; } = RemoteAuthenticationDefaults.ProfilePath; + + /// <summary> + /// Gets or sets the path to the remote endpoint for modifying the settings for the user profile. + /// It might be absolute and point outside of the application. + /// </summary> + public string RemoteProfilePath { get; set; } + + /// <summary> + /// Gets or sets the path to the login page. + /// </summary> + public string LogInPath { get; set; } = RemoteAuthenticationDefaults.LoginPath; + + /// <summary> + /// Gets or sets the path to the login callback page. + /// </summary> + public string LogInCallbackPath { get; set; } = RemoteAuthenticationDefaults.LoginCallbackPath; + + /// <summary> + /// Gets or sets the path to the login failed page. + /// </summary> + public string LogInFailedPath { get; set; } = RemoteAuthenticationDefaults.LoginFailedPath; + + /// <summary> + /// Gets or sets the path to the logout page. + /// </summary> + public string LogOutPath { get; set; } = RemoteAuthenticationDefaults.LogoutPath; + + /// <summary> + /// Gets or sets the path to the logout callback page. + /// </summary> + public string LogOutCallbackPath { get; set; } = RemoteAuthenticationDefaults.LogoutCallbackPath; + + /// <summary> + /// Gets or sets the path to the logout failed page. + /// </summary> + public string LogOutFailedPath { get; set; } = RemoteAuthenticationDefaults.LogoutFailedPath; + + /// <summary> + /// Gets or sets the path to the logout succeeded page. + /// </summary> + public string LogOutSucceededPath { get; set; } = RemoteAuthenticationDefaults.LogoutSucceededPath; + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/RemoteAuthenticationOptions.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/RemoteAuthenticationOptions.cs new file mode 100644 index 0000000000000000000000000000000000000000..b20c2f8b261022d878b8104c1fd31ac64a627f72 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/RemoteAuthenticationOptions.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Options for remote authentication. + /// </summary> + /// <typeparam name="TRemoteAuthenticationProviderOptions">The type of the underlying provider options.</typeparam> + public class RemoteAuthenticationOptions<TRemoteAuthenticationProviderOptions> where TRemoteAuthenticationProviderOptions : new() + { + /// <summary> + /// Gets or sets the provider options. + /// </summary> + public TRemoteAuthenticationProviderOptions ProviderOptions { get; } = new TRemoteAuthenticationProviderOptions(); + + /// <summary> + /// Gets or sets the <see cref="RemoteAuthenticationApplicationPathsOptions"/>. + /// </summary> + public RemoteAuthenticationApplicationPathsOptions AuthenticationPaths { get; } = new RemoteAuthenticationApplicationPathsOptions(); + + /// <summary> + /// Gets or sets the <see cref="RemoteAuthenticationUserOptions"/>. + /// </summary> + public RemoteAuthenticationUserOptions UserOptions { get; } = new RemoteAuthenticationUserOptions(); + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/RemoteAuthenticationUserOptions.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/RemoteAuthenticationUserOptions.cs new file mode 100644 index 0000000000000000000000000000000000000000..22e40f9c14bbf2ae5b9aa166e207b6309f7d46f4 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Options/RemoteAuthenticationUserOptions.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents options to use when configuring the <see cref="System.Security.Claims.ClaimsPrincipal"/> for a user. + /// </summary> + public class RemoteAuthenticationUserOptions + { + /// <summary> + /// Gets or sets the claim type to use for the user name. + /// </summary> + public string NameClaim { get; set; } = "name"; + + /// <summary> + /// Gets or sets the claim type to use for the user roles. + /// </summary> + public string RoleClaim { get; set; } + + /// <summary> + /// Gets or sets the claim type to use for the user scopes. + /// </summary> + public string ScopeClaim { get; set; } + + /// <summary> + /// Gets or sets the value to use for the <see cref="System.Security.Claims.ClaimsIdentity.AuthenticationType"/>. + /// </summary> + public string AuthenticationType { get; set; } + } +} diff --git a/src/Components/Blazor/Blazor/src/Properties/AssemblyInfo.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Properties/AssemblyInfo.cs similarity index 100% rename from src/Components/Blazor/Blazor/src/Properties/AssemblyInfo.cs rename to src/Components/WebAssembly/WebAssembly.Authentication/src/Properties/AssemblyInfo.cs diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/QueryStringHelper.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/QueryStringHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..4986e0bf041be228b7f64d2485e5213ceeddc942 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/QueryStringHelper.cs @@ -0,0 +1,76 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + internal class QueryStringHelper + { + public static string GetParameter(string queryString, string key) + { + if (string.IsNullOrEmpty(queryString) || queryString == "?") + { + return null; + } + + var scanIndex = 0; + if (queryString[0] == '?') + { + scanIndex = 1; + } + + var textLength = queryString.Length; + var equalIndex = queryString.IndexOf('='); + if (equalIndex == -1) + { + equalIndex = textLength; + } + + while (scanIndex < textLength) + { + var ampersandIndex = queryString.IndexOf('&', scanIndex); + if (ampersandIndex == -1) + { + ampersandIndex = textLength; + } + + if (equalIndex < ampersandIndex) + { + while (scanIndex != equalIndex && char.IsWhiteSpace(queryString[scanIndex])) + { + ++scanIndex; + } + var name = queryString[scanIndex..equalIndex]; + var value = queryString.Substring(equalIndex + 1, ampersandIndex - equalIndex - 1); + var processedName = Uri.UnescapeDataString(name.Replace('+', ' ')); + if (string.Equals(processedName, key, StringComparison.OrdinalIgnoreCase)) + { + return Uri.UnescapeDataString(value.Replace('+', ' ')); + } + + equalIndex = queryString.IndexOf('=', ampersandIndex); + if (equalIndex == -1) + { + equalIndex = textLength; + } + } + else + { + if (ampersandIndex > scanIndex) + { + var value = queryString[scanIndex..ampersandIndex]; + if (string.Equals(value, key, StringComparison.OrdinalIgnoreCase)) + { + return string.Empty; + } + } + } + + scanIndex = ampersandIndex + 1; + } + + return null; + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticationBuilder.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticationBuilder.cs new file mode 100644 index 0000000000000000000000000000000000000000..a321a37d78a50d833960f6e1d3c2d579a758ab98 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticationBuilder.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + internal class RemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount> + : IRemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount> + where TRemoteAuthenticationState : RemoteAuthenticationState + where TAccount : RemoteUserAccount + { + public RemoteAuthenticationBuilder(IServiceCollection services) => Services = services; + + public IServiceCollection Services { get; } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticationBuilderExtensions.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticationBuilderExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..471b8cef7a006a0efb91298c6bf0b3bbc93c579c --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticationBuilderExtensions.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// <summary> + /// Extensions for remote authentication services. + /// </summary> + public static class RemoteAuthenticationBuilderExtensions + { + /// <summary> + /// Replaces the existing <see cref="AccountClaimsPrincipalFactory{TAccount}"/> with the user factory defined by <typeparamref name="TAccountClaimsPrincipalFactory"/>. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The remote authentication state.</typeparam> + /// <typeparam name="TAccount">The account type.</typeparam> + /// <typeparam name="TAccountClaimsPrincipalFactory">The new user factory type.</typeparam> + /// <param name="builder">The <see cref="IRemoteAuthenticationBuilder{TRemoteAuthenticationState, TAccount}"/>.</param> + /// <returns>The <see cref="IRemoteAuthenticationBuilder{TRemoteAuthenticationState, TAccount}"/>.</returns> + public static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount> AddAccountClaimsPrincipalFactory<TRemoteAuthenticationState, TAccount, TAccountClaimsPrincipalFactory>( + this IRemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount> builder) + where TRemoteAuthenticationState : RemoteAuthenticationState, new() + where TAccount : RemoteUserAccount + where TAccountClaimsPrincipalFactory : AccountClaimsPrincipalFactory<TAccount> + { + builder.Services.Replace(ServiceDescriptor.Scoped<AccountClaimsPrincipalFactory<TAccount>, TAccountClaimsPrincipalFactory>()); + + return builder; + } + + /// <summary> + /// Replaces the existing <see cref="AccountClaimsPrincipalFactory{Account}"/> with the user factory defined by <typeparamref name="TAccountClaimsPrincipalFactory"/>. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The remote authentication state.</typeparam> + /// <typeparam name="TAccountClaimsPrincipalFactory">The new user factory type.</typeparam> + /// <param name="builder">The <see cref="IRemoteAuthenticationBuilder{TRemoteAuthenticationState, Account}"/>.</param> + /// <returns>The <see cref="IRemoteAuthenticationBuilder{TRemoteAuthenticationState, Account}"/>.</returns> + public static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, RemoteUserAccount> AddAccountClaimsPrincipalFactory<TRemoteAuthenticationState, TAccountClaimsPrincipalFactory>( + this IRemoteAuthenticationBuilder<TRemoteAuthenticationState, RemoteUserAccount> builder) + where TRemoteAuthenticationState : RemoteAuthenticationState, new() + where TAccountClaimsPrincipalFactory : AccountClaimsPrincipalFactory<RemoteUserAccount> => builder.AddAccountClaimsPrincipalFactory<TRemoteAuthenticationState, RemoteUserAccount, TAccountClaimsPrincipalFactory>(); + + /// <summary> + /// Replaces the existing <see cref="AccountClaimsPrincipalFactory{TAccount}"/> with the user factory defined by <typeparamref name="TAccountClaimsPrincipalFactory"/>. + /// </summary> + /// <typeparam name="TAccountClaimsPrincipalFactory">The new user factory type.</typeparam> + /// <param name="builder">The <see cref="IRemoteAuthenticationBuilder{RemoteAuthenticationState, Account}"/>.</param> + /// <returns>The <see cref="IRemoteAuthenticationBuilder{RemoteAuthenticationState, Account}"/>.</returns> + public static IRemoteAuthenticationBuilder<RemoteAuthenticationState, RemoteUserAccount> AddAccountClaimsPrincipalFactory<TAccountClaimsPrincipalFactory>( + this IRemoteAuthenticationBuilder<RemoteAuthenticationState, RemoteUserAccount> builder) + where TAccountClaimsPrincipalFactory : AccountClaimsPrincipalFactory<RemoteUserAccount> => builder.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, RemoteUserAccount, TAccountClaimsPrincipalFactory>(); + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticationDefaults.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticationDefaults.cs new file mode 100644 index 0000000000000000000000000000000000000000..51d66c7a9e5f9f316366102469d8bbc61df356b0 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticationDefaults.cs @@ -0,0 +1,56 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents default values for different configurable values used across the library. + /// </summary> + public class RemoteAuthenticationDefaults + { + /// <summary> + /// The default login path. + /// </summary> + public static readonly string LoginPath = "authentication/login"; + + /// <summary> + /// The default login callback path. + /// </summary> + public static readonly string LoginCallbackPath = "authentication/login-callback"; + + /// <summary> + /// The default login failed path. + /// </summary> + public static readonly string LoginFailedPath = "authentication/login-failed"; + + /// <summary> + /// The default logout path. + /// </summary> + public static readonly string LogoutPath = "authentication/logout"; + + /// <summary> + /// The default logout callback path. + /// </summary> + public static readonly string LogoutCallbackPath = "authentication/logout-callback"; + + /// <summary> + /// The default logout failed path. + /// </summary> + public static readonly string LogoutFailedPath = "authentication/logout-failed"; + + /// <summary> + /// The default logout succeeded path. + /// </summary> + public static readonly string LogoutSucceededPath = "authentication/logged-out"; + + /// <summary> + /// The default profile path. + /// </summary> + public static readonly string ProfilePath = "authentication/profile"; + + /// <summary> + /// The default register path. + /// </summary> + public static readonly string RegisterPath = "authentication/register"; + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticatorView.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticatorView.cs new file mode 100644 index 0000000000000000000000000000000000000000..ff93e18bfff7527d06c90a281a12027d07df7536 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticatorView.cs @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// An <see cref="RemoteAuthenticatorViewCore{TAuthenticationState}"/> that uses <see cref="RemoteAuthenticationState"/> as the + /// state to be persisted across authentication operations. + /// </summary> + public class RemoteAuthenticatorView : RemoteAuthenticatorViewCore<RemoteAuthenticationState> + { + /// <summary> + /// Initializes a new instance of <see cref="RemoteAuthenticatorView"/>. + /// </summary> + public RemoteAuthenticatorView() => AuthenticationState = new RemoteAuthenticationState(); + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticatorViewCore.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticatorViewCore.cs new file mode 100644 index 0000000000000000000000000000000000000000..6d76fd02473a4ef935e1e33fc445fa05c31cec2c --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/RemoteAuthenticatorViewCore.cs @@ -0,0 +1,437 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; +using Microsoft.JSInterop; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// A component that handles remote authentication operations in an application. + /// </summary> + /// <typeparam name="TAuthenticationState">The user state type persisted while the operation is in progress. It must be serializable.</typeparam> + public class RemoteAuthenticatorViewCore<TAuthenticationState> : ComponentBase where TAuthenticationState : RemoteAuthenticationState + { + private string _message; + private RemoteAuthenticationApplicationPathsOptions _applicationPaths; + private string _action; + + /// <summary> + /// Gets or sets the <see cref="RemoteAuthenticationActions"/> action the component needs to handle. + /// </summary> + [Parameter] public string Action { get => _action; set => _action = value?.ToLowerInvariant(); } + + /// <summary> + /// Gets or sets the <typeparamref name="TAuthenticationState"/> instance to be preserved during the authentication operation. + /// </summary> + [Parameter] public TAuthenticationState AuthenticationState { get; set; } + + /// <summary> + /// Gets or sets a <see cref="RenderFragment"/> with the UI to display while <see cref="RemoteAuthenticationActions.LogIn"/> is being handled. + /// </summary> + [Parameter] public RenderFragment LoggingIn { get; set; } = DefaultLogInFragment; + + /// <summary> + /// Gets or sets a <see cref="RenderFragment"/> with the UI to display while <see cref="RemoteAuthenticationActions.Register"/> is being handled. + /// </summary> + [Parameter] public RenderFragment Registering { get; set; } + + /// <summary> + /// Gets or sets a <see cref="RenderFragment"/> with the UI to display while <see cref="RemoteAuthenticationActions.Profile"/> is being handled. + /// </summary> + [Parameter] public RenderFragment UserProfile { get; set; } + + /// <summary> + /// Gets or sets a <see cref="RenderFragment"/> with the UI to display while <see cref="RemoteAuthenticationActions.LogInCallback"/> is being handled. + /// </summary> + [Parameter] public RenderFragment CompletingLoggingIn { get; set; } = DefaultLogInCallbackFragment; + + /// <summary> + /// Gets or sets a <see cref="RenderFragment"/> with the UI to display while <see cref="RemoteAuthenticationActions.LogInFailed"/> is being handled. + /// </summary> + [Parameter] public RenderFragment<string> LogInFailed { get; set; } = DefaultLogInFailedFragment; + + /// <summary> + /// Gets or sets a <see cref="RenderFragment"/> with the UI to display while <see cref="RemoteAuthenticationActions.LogOut"/> is being handled. + /// </summary> + [Parameter] public RenderFragment LogOut { get; set; } = DefaultLogOutFragment; + + /// <summary> + /// Gets or sets a <see cref="RenderFragment"/> with the UI to display while <see cref="RemoteAuthenticationActions.LogOutCallback"/> is being handled. + /// </summary> + [Parameter] public RenderFragment CompletingLogOut { get; set; } = DefaultLogOutCallbackFragment; + + /// <summary> + /// Gets or sets a <see cref="RenderFragment"/> with the UI to display while <see cref="RemoteAuthenticationActions.LogOutFailed"/> is being handled. + /// </summary> + [Parameter] public RenderFragment<string> LogOutFailed { get; set; } = DefaultLogOutFailedFragment; + + /// <summary> + /// Gets or sets a <see cref="RenderFragment"/> with the UI to display while <see cref="RemoteAuthenticationActions.LogOutSucceeded"/> is being handled. + /// </summary> + [Parameter] public RenderFragment LogOutSucceeded { get; set; } = DefaultLoggedOutFragment; + + /// <summary> + /// Gets or sets an event callback that will be invoked with the stored authentication state when a log in operation succeeds. + /// </summary> + [Parameter] public EventCallback<TAuthenticationState> OnLogInSucceeded { get; set; } + + /// <summary> + /// Gets or sets an event callback that will be invoked with the stored authentication state when a log out operation succeeds. + /// </summary> + [Parameter] public EventCallback<TAuthenticationState> OnLogOutSucceeded { get; set; } + + /// <summary> + /// Gets or sets the <see cref="IJSRuntime"/> to use for performin JavaScript interop. + /// </summary> + [Inject] internal IJSRuntime JS { get; set; } + + /// <summary> + /// Gets or sets the <see cref="NavigationManager"/> to use for redirecting the browser. + /// </summary> + [Inject] internal NavigationManager Navigation { get; set; } + + /// <summary> + /// Gets or sets the <see cref="IRemoteAuthenticationService{TRemoteAuthenticationState}"/> to use for handling the underlying authentication protocol. + /// </summary> + [Inject] internal IRemoteAuthenticationService<TAuthenticationState> AuthenticationService { get; set; } + + /// <summary> + /// Gets or sets a default <see cref="IRemoteAuthenticationPathsProvider"/> to use as fallback if an <see cref="ApplicationPaths"/> has not been explicitly specified. + /// </summary> +#pragma warning disable PUB0001 // Pubternal type in public API + [Inject] internal IRemoteAuthenticationPathsProvider RemoteApplicationPathsProvider { get; set; } +#pragma warning restore PUB0001 // Pubternal type in public API + + /// <summary> + /// Gets or sets a default <see cref="AuthenticationStateProvider"/> with the current user. + /// </summary> + [Inject] internal AuthenticationStateProvider AuthenticationProvider { get; set; } + + /// <summary> + /// Gets or sets a default <see cref="AuthenticationStateProvider"/> with the current user. + /// </summary> + [Inject] internal SignOutSessionStateManager SignOutManager { get; set; } + + /// <summary> + /// Gets or sets the <see cref="RemoteAuthenticationApplicationPathsOptions"/> with the paths to different authentication pages. + /// </summary> + [Parameter] + public RemoteAuthenticationApplicationPathsOptions ApplicationPaths + { + get => _applicationPaths ?? RemoteApplicationPathsProvider.ApplicationPaths; + set => _applicationPaths = value; + } + + /// <inheritdoc /> + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + base.BuildRenderTree(builder); + switch (Action) + { + case RemoteAuthenticationActions.Profile: + builder.AddContent(0, UserProfile); + break; + case RemoteAuthenticationActions.Register: + builder.AddContent(0, Registering); + break; + case RemoteAuthenticationActions.LogIn: + builder.AddContent(0, LoggingIn); + break; + case RemoteAuthenticationActions.LogInCallback: + builder.AddContent(0, CompletingLoggingIn); + break; + case RemoteAuthenticationActions.LogInFailed: + builder.AddContent(0, LogInFailed(_message)); + break; + case RemoteAuthenticationActions.LogOut: + builder.AddContent(0, LogOut); + break; + case RemoteAuthenticationActions.LogOutCallback: + builder.AddContent(0, CompletingLogOut); + break; + case RemoteAuthenticationActions.LogOutFailed: + builder.AddContent(0, LogOutFailed(_message)); + break; + case RemoteAuthenticationActions.LogOutSucceeded: + builder.AddContent(0, LogOutSucceeded); + break; + default: + throw new InvalidOperationException($"Invalid action '{Action}'."); + } + } + + /// <inheritdoc /> + protected override async Task OnParametersSetAsync() + { + switch (Action) + { + case RemoteAuthenticationActions.LogIn: + await ProcessLogIn(GetReturnUrl(state: null)); + break; + case RemoteAuthenticationActions.LogInCallback: + await ProcessLogInCallback(); + break; + case RemoteAuthenticationActions.LogInFailed: + break; + case RemoteAuthenticationActions.Profile: + if (ApplicationPaths.RemoteProfilePath == null) + { + UserProfile ??= ProfileNotSupportedFragment; + } + else + { + UserProfile ??= LoggingIn; + await RedirectToProfile(); + } + break; + case RemoteAuthenticationActions.Register: + if (ApplicationPaths.RemoteRegisterPath == null) + { + Registering ??= RegisterNotSupportedFragment; + } + else + { + Registering ??= LoggingIn; + } + + await RedirectToRegister(); + break; + case RemoteAuthenticationActions.LogOut: + await ProcessLogOut(GetReturnUrl(state: null, Navigation.ToAbsoluteUri(ApplicationPaths.LogOutSucceededPath).AbsoluteUri)); + break; + case RemoteAuthenticationActions.LogOutCallback: + await ProcessLogOutCallback(); + break; + case RemoteAuthenticationActions.LogOutFailed: + break; + case RemoteAuthenticationActions.LogOutSucceeded: + break; + default: + throw new InvalidOperationException($"Invalid action '{Action}'."); + } + } + + private async Task ProcessLogIn(string returnUrl) + { + AuthenticationState.ReturnUrl = returnUrl; + var result = await AuthenticationService.SignInAsync(new RemoteAuthenticationContext<TAuthenticationState> + { + State = AuthenticationState + }); + + switch (result.Status) + { + case RemoteAuthenticationStatus.Redirect: + break; + case RemoteAuthenticationStatus.Success: + await OnLogInSucceeded.InvokeAsync(result.State); + await NavigateToReturnUrl(GetReturnUrl(result.State, returnUrl)); + break; + case RemoteAuthenticationStatus.Failure: + _message = result.ErrorMessage; + Navigation.NavigateTo(ApplicationPaths.LogInFailedPath); + break; + case RemoteAuthenticationStatus.OperationCompleted: + default: + throw new InvalidOperationException($"Invalid authentication result status '{result.Status}'."); + } + } + + private async Task ProcessLogInCallback() + { + var url = Navigation.Uri; + var result = await AuthenticationService.CompleteSignInAsync(new RemoteAuthenticationContext<TAuthenticationState> { Url = url }); + switch (result.Status) + { + case RemoteAuthenticationStatus.Redirect: + // There should not be any redirects as the only time CompleteSignInAsync finishes + // is when we are doing a redirect sign in flow. + throw new InvalidOperationException("Should not redirect."); + case RemoteAuthenticationStatus.Success: + await OnLogInSucceeded.InvokeAsync(result.State); + await NavigateToReturnUrl(GetReturnUrl(result.State)); + break; + case RemoteAuthenticationStatus.OperationCompleted: + break; + case RemoteAuthenticationStatus.Failure: + var uri = Navigation.ToAbsoluteUri($"{ApplicationPaths.LogInFailedPath}?message={Uri.EscapeDataString(result.ErrorMessage)}").ToString(); + await NavigateToReturnUrl(uri); + break; + default: + throw new InvalidOperationException($"Invalid authentication result status '{result.Status}'."); + } + } + + private async Task ProcessLogOut(string returnUrl) + { + if (!await SignOutManager.ValidateSignOutState()) + { + var uri = $"{Navigation.ToAbsoluteUri(ApplicationPaths.LogOutFailedPath)}?message={Uri.EscapeDataString("The logout was not initiated from within the page.")}"; + Navigation.NavigateTo(uri); + + return; + } + + AuthenticationState.ReturnUrl = returnUrl; + + var state = await AuthenticationProvider.GetAuthenticationStateAsync(); + var isauthenticated = state.User.Identity.IsAuthenticated; + if (isauthenticated) + { + var result = await AuthenticationService.SignOutAsync(new RemoteAuthenticationContext<TAuthenticationState> { State = AuthenticationState }); + switch (result.Status) + { + case RemoteAuthenticationStatus.Redirect: + break; + case RemoteAuthenticationStatus.Success: + await OnLogOutSucceeded.InvokeAsync(result.State); + await NavigateToReturnUrl(returnUrl); + break; + case RemoteAuthenticationStatus.OperationCompleted: + break; + case RemoteAuthenticationStatus.Failure: + _message = result.ErrorMessage; + Navigation.NavigateTo(ApplicationPaths.LogOutFailedPath); + break; + default: + throw new InvalidOperationException($"Invalid authentication result status."); + } + } + else + { + await NavigateToReturnUrl(returnUrl); + } + } + + private async Task ProcessLogOutCallback() + { + var result = await AuthenticationService.CompleteSignOutAsync(new RemoteAuthenticationContext<TAuthenticationState> { Url = Navigation.Uri }); + switch (result.Status) + { + case RemoteAuthenticationStatus.Redirect: + // There should not be any redirects as the only time completeAuthentication finishes + // is when we are doing a redirect sign in flow. + throw new InvalidOperationException("Should not redirect."); + case RemoteAuthenticationStatus.Success: + await OnLogOutSucceeded.InvokeAsync(result.State); + await NavigateToReturnUrl(GetReturnUrl(result.State, Navigation.ToAbsoluteUri(ApplicationPaths.LogOutSucceededPath).ToString())); + break; + case RemoteAuthenticationStatus.OperationCompleted: + break; + case RemoteAuthenticationStatus.Failure: + var uri = Navigation.ToAbsoluteUri($"{ApplicationPaths.LogOutFailedPath}?message={Uri.EscapeDataString(result.ErrorMessage)}").ToString(); + await NavigateToReturnUrl(uri); + break; + default: + throw new InvalidOperationException($"Invalid authentication result status."); + } + } + + private string GetReturnUrl(TAuthenticationState state, string defaultReturnUrl = null) + { + if (state?.ReturnUrl != null) + { + return state.ReturnUrl; + } + + var fromQuery = QueryStringHelper.GetParameter(new Uri(Navigation.Uri).Query, "returnUrl"); + if (!string.IsNullOrWhiteSpace(fromQuery) && !fromQuery.StartsWith(Navigation.BaseUri)) + { + // This is an extra check to prevent open redirects. + throw new InvalidOperationException("Invalid return url. The return url needs to have the same origin as the current page."); + } + + return fromQuery ?? defaultReturnUrl ?? Navigation.BaseUri; + } + + private async Task NavigateToReturnUrl(string returnUrl) => await JS.InvokeVoidAsync("Blazor.navigateTo", returnUrl, false, true); + + private ValueTask RedirectToRegister() + { + var loginUrl = Navigation.ToAbsoluteUri(ApplicationPaths.LogInPath).PathAndQuery; + var registerUrl = Navigation.ToAbsoluteUri($"{ApplicationPaths.RemoteRegisterPath}?returnUrl={Uri.EscapeDataString(loginUrl)}").PathAndQuery; + + return JS.InvokeVoidAsync("location.replace", registerUrl); + } + + private ValueTask RedirectToProfile() => JS.InvokeVoidAsync("location.replace", Navigation.ToAbsoluteUri(ApplicationPaths.RemoteProfilePath).PathAndQuery); + + private static void DefaultLogInFragment(RenderTreeBuilder builder) + { + builder.OpenElement(0, "p"); + builder.AddContent(1, "Checking login state..."); + builder.CloseElement(); + } + + private static void RegisterNotSupportedFragment(RenderTreeBuilder builder) + { + builder.OpenElement(0, "p"); + builder.AddContent(1, "Registration is not supported."); + builder.CloseElement(); + } + + private static void ProfileNotSupportedFragment(RenderTreeBuilder builder) + { + builder.OpenElement(0, "p"); + builder.AddContent(1, "Editing the profile is not supported."); + builder.CloseElement(); + } + + private static void DefaultLogInCallbackFragment(RenderTreeBuilder builder) + { + builder.OpenElement(0, "p"); + builder.AddContent(1, "Completing login..."); + builder.CloseElement(); + } + + private static RenderFragment DefaultLogInFailedFragment(string message) + { + return builder => + { + builder.OpenElement(0, "p"); + builder.AddContent(1, "There was an error trying to log you in: '"); + builder.AddContent(2, message); + builder.AddContent(3, "'"); + builder.CloseElement(); + }; + } + + private static void DefaultLogOutFragment(RenderTreeBuilder builder) + { + builder.OpenElement(0, "p"); + builder.AddContent(1, "Processing logout..."); + builder.CloseElement(); + } + + private static void DefaultLogOutCallbackFragment(RenderTreeBuilder builder) + { + builder.OpenElement(0, "p"); + builder.AddContent(1, "Processing logout callback..."); + builder.CloseElement(); + } + + private static RenderFragment DefaultLogOutFailedFragment(string message) + { + return builder => + { + builder.OpenElement(0, "p"); + builder.AddContent(1, "There was an error trying to log you out: '"); + builder.AddContent(2, message); + builder.AddContent(3, "'"); + builder.CloseElement(); + }; + } + + private static void DefaultLoggedOutFragment(RenderTreeBuilder builder) + { + builder.OpenElement(0, "p"); + builder.AddContent(1, "You are logged out."); + builder.CloseElement(); + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenProviderAccessor.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenProviderAccessor.cs new file mode 100644 index 0000000000000000000000000000000000000000..5aac0607151da5be3b919576016b24a14dfe52fa --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenProviderAccessor.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal +{ + internal class AccessTokenProviderAccessor : IAccessTokenProviderAccessor + { + private readonly IServiceProvider _provider; + private IAccessTokenProvider _tokenProvider; + + public AccessTokenProviderAccessor(IServiceProvider provider) => _provider = provider; + + public IAccessTokenProvider TokenProvider => _tokenProvider ??= _provider.GetRequiredService<IAccessTokenProvider>(); + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenRequestOptions.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenRequestOptions.cs new file mode 100644 index 0000000000000000000000000000000000000000..cffb2fd3dc46804755b0b114d20b1e488a9ac4b4 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenRequestOptions.cs @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents the options for provisioning an access token on behalf of a user. + /// </summary> + public class AccessTokenRequestOptions + { + /// <summary> + /// Gets or sets the list of scopes to request for the token. + /// </summary> + public IEnumerable<string> Scopes { get; set; } + + /// <summary> + /// Gets or sets a specific return url to use for returning the user back to the application if it needs to be + /// redirected elsewhere in order to provision the token. + /// </summary> + public string ReturnUrl { get; set; } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenResult.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenResult.cs new file mode 100644 index 0000000000000000000000000000000000000000..8fe4a707b0a97134568e080518145f470c8072fd --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenResult.cs @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents the result of trying to provision an access token. + /// </summary> + public class AccessTokenResult + { + private readonly AccessToken _token; + + /// <summary> + /// Initializes a new instance of <see cref="AccessTokenResult"/>. + /// </summary> + /// <param name="status">The status of the result.</param> + /// <param name="token">The <see cref="AccessToken"/> in case it was successful.</param> + /// <param name="redirectUrl">The redirect uri to go to for provisioning the token.</param> + public AccessTokenResult(AccessTokenResultStatus status, AccessToken token, string redirectUrl) + { + Status = status; + _token = token; + RedirectUrl = redirectUrl; + } + + /// <summary> + /// Gets or sets the status of the current operation. See <see cref="AccessTokenResultStatus"/> for a list of statuses. + /// </summary> + public AccessTokenResultStatus Status { get; } + + /// <summary> + /// Gets or sets the URL to redirect to if <see cref="Status"/> is <see cref="AccessTokenResultStatus.RequiresRedirect"/>. + /// </summary> + public string RedirectUrl { get; } + + /// <summary> + /// Determines whether the token request was successful and makes the <see cref="AccessToken"/> available for use when it is. + /// </summary> + /// <param name="accessToken">The <see cref="AccessToken"/> if the request was successful.</param> + /// <returns><c>true</c> when the token request is successful; <c>false</c> otherwise.</returns> + public bool TryGetToken(out AccessToken accessToken) + { + if (Status == AccessTokenResultStatus.Success) + { + accessToken = _token; + return true; + } + else + { + accessToken = null; + return false; + } + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenResultStatus.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenResultStatus.cs new file mode 100644 index 0000000000000000000000000000000000000000..15d311413f2565d9452cafc8f4506944d14b12e2 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccessTokenResultStatus.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents the possible results from trying to acquire an access token. + /// </summary> + public enum AccessTokenResultStatus + { + /// <summary> + /// The token was successfully acquired. + /// </summary> + Success, + + /// <summary> + /// A redirect is needed in order to provision the token. + /// </summary> + RequiresRedirect, + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccountClaimsPrincipalFactory.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccountClaimsPrincipalFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..d81a802fa095f4a295e9da409e0b123a2df5f1b4 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AccountClaimsPrincipalFactory.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Security.Claims; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Converts <see cref="RemoteUserAccount" /> into a <see cref="ClaimsPrincipal"/>. + /// </summary> + /// <typeparam name="TAccount">The account type.</typeparam> + public class AccountClaimsPrincipalFactory<TAccount> where TAccount : RemoteUserAccount + { + private readonly IAccessTokenProviderAccessor _accessor; + +#pragma warning disable PUB0001 // Pubternal type in public API + public AccountClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor) => _accessor = accessor; + + /// <summary> + /// Gets or sets the <see cref="IAccessTokenProvider"/>. + /// </summary> + public IAccessTokenProvider TokenProvider => _accessor.TokenProvider; + + /// <summary> + /// Converts the <paramref name="account"/> into the final <see cref="ClaimsPrincipal"/>. + /// </summary> + /// <param name="account">The account.</param> + /// <param name="options">The <see cref="RemoteAuthenticationUserOptions"/> to configure the <see cref="ClaimsPrincipal"/> with.</param> + /// <returns>A <see cref="ValueTask{TResult}"/>that will contain the <see cref="ClaimsPrincipal"/> user when completed.</returns> + public virtual ValueTask<ClaimsPrincipal> CreateUserAsync( + TAccount account, + RemoteAuthenticationUserOptions options) + { + var identity = account != null ? new ClaimsIdentity( + options.AuthenticationType, + options.NameClaim, + options.RoleClaim) : new ClaimsIdentity(); + + if (account != null) + { + foreach (var kvp in account.AdditionalProperties) + { + var name = kvp.Key; + var value = kvp.Value; + if (value != null || + (value is JsonElement element && element.ValueKind != JsonValueKind.Undefined && element.ValueKind != JsonValueKind.Null)) + { + identity.AddClaim(new Claim(name, value.ToString())); + } + } + } + + return new ValueTask<ClaimsPrincipal>(new ClaimsPrincipal(identity)); + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AuthorizationMessageHandler.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AuthorizationMessageHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..ad019ff0808cfd7f90b0dec13e797b6a52dbffed --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/AuthorizationMessageHandler.cs @@ -0,0 +1,124 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// A <see cref="DelegatingHandler"/> that attaches access tokens to outgoing <see cref="HttpResponseMessage"/> instances. + /// Access tokens will only be added when the request URI is within one of the base addresses configured using + /// <see cref="ConfigureHandler(IEnumerable{string}, IEnumerable{string}, string)"/>. + /// </summary> + public class AuthorizationMessageHandler : DelegatingHandler + { + private readonly IAccessTokenProvider _provider; + private readonly NavigationManager _navigation; + private AccessToken _lastToken; + private AuthenticationHeaderValue _cachedHeader; + private Uri[] _authorizedUris; + private AccessTokenRequestOptions _tokenOptions; + + /// <summary> + /// Initializes a new instance of <see cref="AuthorizationMessageHandler"/>. + /// </summary> + /// <param name="provider">The <see cref="IAccessTokenProvider"/> to use for provisioning tokens.</param> + /// <param name="navigation">The <see cref="NavigationManager"/> to use for performing redirections.</param> + public AuthorizationMessageHandler( + IAccessTokenProvider provider, + NavigationManager navigation) + { + _provider = provider; + _navigation = navigation; + } + + /// <inheritdoc /> + protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var now = DateTimeOffset.Now; + if (_authorizedUris == null) + { + throw new InvalidOperationException($"The '{nameof(AuthorizationMessageHandler)}' is not configured. " + + $"Call '{nameof(AuthorizationMessageHandler.ConfigureHandler)}' and provide a list of endpoint urls to attach the token to."); + } + + if (_authorizedUris.Any(uri => uri.IsBaseOf(request.RequestUri))) + { + if (_lastToken == null || now >= _lastToken.Expires.AddMinutes(-5)) + { + var tokenResult = _tokenOptions != null ? + await _provider.RequestAccessToken(_tokenOptions) : + await _provider.RequestAccessToken(); + + if (tokenResult.TryGetToken(out var token)) + { + _lastToken = token; + _cachedHeader = new AuthenticationHeaderValue("Bearer", _lastToken.Value); + } + else + { + throw new AccessTokenNotAvailableException(_navigation, tokenResult, _tokenOptions?.Scopes); + } + } + + // We don't try to handle 401s and retry the request with a new token automatically since that would mean we need to copy the request + // headers and buffer the body and we expect that the user instead handles the 401s. (Also, we can't really handle all 401s as we might + // not be able to provision a token without user interaction). + request.Headers.Authorization = _cachedHeader; + } + + return await base.SendAsync(request, cancellationToken); + } + + /// <summary> + /// Configures this handler to authorize outbound HTTP requests using an access token. The access token is only attached if only attached if at least one of + /// <paramref name="authorizedUrls" /> is a base of <see cref="HttpRequestMessage.RequestUri" />. + /// </summary> + /// <param name="authorizedUrls">The base addresses of endpoint URLs to which the token will be attached.</param> + /// <param name="scopes">The list of scopes to use when requesting an access token.</param> + /// <param name="returnUrl">The return URL to use in case there is an issue provisioning the token and a redirection to the + /// identity provider is necessary. + /// </param> + /// <returns>This <see cref="AuthorizationMessageHandler"/>.</returns> + public AuthorizationMessageHandler ConfigureHandler( + IEnumerable<string> authorizedUrls, + IEnumerable<string> scopes = null, + string returnUrl = null) + { + if (_authorizedUris != null) + { + throw new InvalidOperationException("Handler already configured."); + } + + if (authorizedUrls == null) + { + throw new ArgumentNullException(nameof(authorizedUrls)); + } + + var uris = authorizedUrls.Select(uri => new Uri(uri, UriKind.Absolute)).ToArray(); + if (uris.Length == 0) + { + throw new ArgumentException("At least one URL must be configured.", nameof(authorizedUrls)); + } + + _authorizedUris = uris; + var scopesList = scopes?.ToArray(); + if (scopesList != null || returnUrl != null) + { + _tokenOptions = new AccessTokenRequestOptions + { + Scopes = scopesList, + ReturnUrl = returnUrl + }; + } + + return this; + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/BaseAddressAuthorizationMessageHandler.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/BaseAddressAuthorizationMessageHandler.cs new file mode 100644 index 0000000000000000000000000000000000000000..5fe5511f1ef4903358ff99af13861859644dbe9a --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/BaseAddressAuthorizationMessageHandler.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Net.Http; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// A <see cref="DelegatingHandler"/> that attaches access tokens to outgoing <see cref="HttpResponseMessage"/> instances. + /// Access tokens will only be added when the request URI is within the application's base URI. + /// </summary> + public class BaseAddressAuthorizationMessageHandler : AuthorizationMessageHandler + { + /// <summary> + /// Initializes a new instance of <see cref="BaseAddressAuthorizationMessageHandler"/>. + /// </summary> + /// <param name="provider">The <see cref="IAccessTokenProvider"/> to use for requesting tokens.</param> + /// <param name="navigationManager">The <see cref="NavigationManager"/> used to compute the base address.</param> + public BaseAddressAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigationManager) + : base(provider, navigationManager) + { + ConfigureHandler(new[] { navigationManager.BaseUri }); + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/DefaultRemoteApplicationPathsProvider.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/DefaultRemoteApplicationPathsProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..f37da27fb94fc0e4a0ef4af6666f56bbdc706c38 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/DefaultRemoteApplicationPathsProvider.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + internal class DefaultRemoteApplicationPathsProvider<TProviderOptions> : IRemoteAuthenticationPathsProvider where TProviderOptions : class, new() + { + private readonly IOptions<RemoteAuthenticationOptions<TProviderOptions>> _options; + + public DefaultRemoteApplicationPathsProvider(IOptions<RemoteAuthenticationOptions<TProviderOptions>> options) + { + _options = options; + } + + public RemoteAuthenticationApplicationPathsOptions ApplicationPaths => _options.Value.AuthenticationPaths; + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/ExpiredTokenException.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/ExpiredTokenException.cs new file mode 100644 index 0000000000000000000000000000000000000000..762d4de4292c0f23d40e058f8295a9965199f13c --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/ExpiredTokenException.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// An <see cref="Exception"/> that is thrown when an <see cref="AuthorizationMessageHandler"/> instance + /// is not able to provision an access token. + /// </summary> + public class AccessTokenNotAvailableException : Exception + { + private readonly NavigationManager _navigation; + private readonly AccessTokenResult _tokenResult; + + public AccessTokenNotAvailableException( + NavigationManager navigation, + AccessTokenResult tokenResult, + IEnumerable<string> scopes) + : base(message: "Unable to provision an access token for the requested scopes: " + + scopes != null ? $"'{string.Join(", ", scopes ?? Array.Empty<string>())}'" : "(default scopes)") + { + _tokenResult = tokenResult; + _navigation = navigation; + } + + public void Redirect() => _navigation.NavigateTo(_tokenResult.RedirectUrl); + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/IAccessTokenProvider.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/IAccessTokenProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..9a9932ecea10d517bb13d1ee26a83f2a830cec81 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/IAccessTokenProvider.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents a contract for services capable of provisioning access tokens for an application. + /// </summary> + public interface IAccessTokenProvider + { + /// <summary> + /// Tries to get an access token for the current user with the default set of permissions. + /// </summary> + /// <returns>A <see cref="ValueTask{AccessTokenResult}"/> that will contain the <see cref="AccessTokenResult"/> when completed.</returns> + ValueTask<AccessTokenResult> RequestAccessToken(); + + /// <summary> + /// Tries to get an access token with the options specified in <see cref="AccessTokenRequestOptions"/>. + /// </summary> + /// <param name="options">The <see cref="AccessTokenRequestOptions"/> for provisioning the access token.</param> + /// <returns>A <see cref="ValueTask{AccessTokenResult}"/> that will contain the <see cref="AccessTokenResult"/> when completed.</returns> + ValueTask<AccessTokenResult> RequestAccessToken(AccessTokenRequestOptions options); + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/IRemoteAuthenticationService.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/IRemoteAuthenticationService.cs new file mode 100644 index 0000000000000000000000000000000000000000..ebad4494bab52119dba5a02246de30e17c0186da --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/IRemoteAuthenticationService.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Security.Claims; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Represents a contract for services that perform authentication operations for a Blazor WebAssembly application. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The state to be persisted across authentication operations.</typeparam> + public interface IRemoteAuthenticationService<TRemoteAuthenticationState> + where TRemoteAuthenticationState : RemoteAuthenticationState + { + /// <summary> + /// Signs in a user. + /// </summary> + /// <param name="context">The <see cref="RemoteAuthenticationContext{TRemoteAuthenticationState}"/> for authenticating the user.</param> + /// <returns>The result of the authentication operation.</returns> + Task<RemoteAuthenticationResult<TRemoteAuthenticationState>> SignInAsync(RemoteAuthenticationContext<TRemoteAuthenticationState> context); + + /// <summary> + /// Completes the sign in operation for a user when it is performed outside of the application origin via a redirect operation followed + /// by a redirect callback to a page in the application. + /// </summary> + /// <param name="context">The <see cref="RemoteAuthenticationContext{TRemoteAuthenticationState}"/> for authenticating the user.</param> + /// <returns>The result of the authentication operation.</returns> + Task<RemoteAuthenticationResult<TRemoteAuthenticationState>> CompleteSignInAsync( + RemoteAuthenticationContext<TRemoteAuthenticationState> context); + + /// <summary> + /// Signs out a user. + /// </summary> + /// <param name="context">The <see cref="RemoteAuthenticationContext{TRemoteAuthenticationState}"/> for authenticating the user.</param> + /// <returns>The result of the authentication operation.</returns> + Task<RemoteAuthenticationResult<TRemoteAuthenticationState>> SignOutAsync( + RemoteAuthenticationContext<TRemoteAuthenticationState> context); + + /// <summary> + /// Completes the sign out operation for a user when it is performed outside of the application origin via a redirect operation followed + /// by a redirect callback to a page in the application. + /// </summary> + /// <param name="context">The <see cref="RemoteAuthenticationContext{TRemoteAuthenticationState}"/> for authenticating the user.</param> + /// <returns>The result of the authentication operation.</returns> + Task<RemoteAuthenticationResult<TRemoteAuthenticationState>> CompleteSignOutAsync( + RemoteAuthenticationContext<TRemoteAuthenticationState> context); + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/RemoteAuthenticationService.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/RemoteAuthenticationService.cs new file mode 100644 index 0000000000000000000000000000000000000000..30e07e27ba401af2faad4dc92854e19ee350f788 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/RemoteAuthenticationService.cs @@ -0,0 +1,277 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Security.Claims; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.Extensions.Options; +using Microsoft.JSInterop; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// The default implementation for <see cref="IRemoteAuthenticationService{TRemoteAuthenticationState}"/> that uses JS interop to authenticate the user. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The state to preserve across authentication operations.</typeparam> + /// <typeparam name="TAccount">The type of the <see cref="RemoteUserAccount" />.</typeparam> + /// <typeparam name="TProviderOptions">The options to be passed down to the underlying JavaScript library handling the authentication operations.</typeparam> + public class RemoteAuthenticationService<TRemoteAuthenticationState, TAccount, TProviderOptions> : + AuthenticationStateProvider, + IRemoteAuthenticationService<TRemoteAuthenticationState>, + IAccessTokenProvider + where TRemoteAuthenticationState : RemoteAuthenticationState + where TProviderOptions : new() + where TAccount : RemoteUserAccount + { + private static readonly TimeSpan _userCacheRefreshInterval = TimeSpan.FromSeconds(60); + private bool _initialized = false; + + // This defaults to 1/1/1970 + private DateTimeOffset _userLastCheck = DateTimeOffset.FromUnixTimeSeconds(0); + private ClaimsPrincipal _cachedUser = new ClaimsPrincipal(new ClaimsIdentity()); + + /// <summary> + /// Gets the <see cref="IJSRuntime"/> to use for performing JavaScript interop operations. + /// </summary> + protected IJSRuntime JsRuntime { get; } + + /// <summary> + /// Gets the <see cref="NavigationManager"/> used to compute absolute urls. + /// </summary> + protected NavigationManager Navigation { get; } + + /// <summary> + /// Gets the <see cref="AccountClaimsPrincipalFactory{TAccount}"/> to map accounts to <see cref="ClaimsPrincipal"/>. + /// </summary> + protected AccountClaimsPrincipalFactory<TAccount> AccountClaimsPrincipalFactory { get; } + + /// <summary> + /// Gets the options for the underlying JavaScript library handling the authentication operations. + /// </summary> + protected RemoteAuthenticationOptions<TProviderOptions> Options { get; } + + /// <summary> + /// Initializes a new instance. + /// </summary> + /// <param name="jsRuntime">The <see cref="IJSRuntime"/> to use for performing JavaScript interop operations.</param> + /// <param name="options">The options to be passed down to the underlying JavaScript library handling the authentication operations.</param> + /// <param name="navigation">The <see cref="NavigationManager"/> used to generate URLs.</param> + /// <param name="accountClaimsPrincipalFactory">The <see cref="AccountClaimsPrincipalFactory{TAccount}"/> used to generate the <see cref="ClaimsPrincipal"/> for the user.</param> + public RemoteAuthenticationService( + IJSRuntime jsRuntime, + IOptions<RemoteAuthenticationOptions<TProviderOptions>> options, + NavigationManager navigation, + AccountClaimsPrincipalFactory<TAccount> accountClaimsPrincipalFactory) + { + JsRuntime = jsRuntime; + Navigation = navigation; + AccountClaimsPrincipalFactory = accountClaimsPrincipalFactory; + Options = options.Value; + } + + /// <inheritdoc /> + public override async Task<AuthenticationState> GetAuthenticationStateAsync() => new AuthenticationState(await GetUser(useCache: true)); + + /// <inheritdoc /> + public virtual async Task<RemoteAuthenticationResult<TRemoteAuthenticationState>> SignInAsync( + RemoteAuthenticationContext<TRemoteAuthenticationState> context) + { + await EnsureAuthService(); + var internalResult = await JsRuntime.InvokeAsync<InternalRemoteAuthenticationResult<TRemoteAuthenticationState>>("AuthenticationService.signIn", context.State); + var result = internalResult.Convert(); + if (result.Status == RemoteAuthenticationStatus.Success) + { + var getUserTask = GetUser(); + await getUserTask; + UpdateUser(getUserTask); + } + + return result; + } + + /// <inheritdoc /> + public virtual async Task<RemoteAuthenticationResult<TRemoteAuthenticationState>> CompleteSignInAsync( + RemoteAuthenticationContext<TRemoteAuthenticationState> context) + { + await EnsureAuthService(); + var internalResult = await JsRuntime.InvokeAsync<InternalRemoteAuthenticationResult<TRemoteAuthenticationState>>("AuthenticationService.completeSignIn", context.Url); + var result = internalResult.Convert(); + if (result.Status == RemoteAuthenticationStatus.Success) + { + var getUserTask = GetUser(); + await getUserTask; + UpdateUser(getUserTask); + } + + return result; + } + + /// <inheritdoc /> + public virtual async Task<RemoteAuthenticationResult<TRemoteAuthenticationState>> SignOutAsync( + RemoteAuthenticationContext<TRemoteAuthenticationState> context) + { + await EnsureAuthService(); + var internalResult = await JsRuntime.InvokeAsync<InternalRemoteAuthenticationResult<TRemoteAuthenticationState>>("AuthenticationService.signOut", context.State); + var result = internalResult.Convert(); + if (result.Status == RemoteAuthenticationStatus.Success) + { + var getUserTask = GetUser(); + await getUserTask; + UpdateUser(getUserTask); + } + + return result; + } + + /// <inheritdoc /> + public virtual async Task<RemoteAuthenticationResult<TRemoteAuthenticationState>> CompleteSignOutAsync( + RemoteAuthenticationContext<TRemoteAuthenticationState> context) + { + await EnsureAuthService(); + var internalResult = await JsRuntime.InvokeAsync<InternalRemoteAuthenticationResult<TRemoteAuthenticationState>>("AuthenticationService.completeSignOut", context.Url); + var result = internalResult.Convert(); + if (result.Status == RemoteAuthenticationStatus.Success) + { + var getUserTask = GetUser(); + await getUserTask; + UpdateUser(getUserTask); + } + + return result; + } + + /// <inheritdoc /> + public virtual async ValueTask<AccessTokenResult> RequestAccessToken() + { + await EnsureAuthService(); + var result = await JsRuntime.InvokeAsync<InternalAccessTokenResult>("AuthenticationService.getAccessToken"); + + if (!Enum.TryParse<AccessTokenResultStatus>(result.Status, ignoreCase: true, out var parsedStatus)) + { + throw new InvalidOperationException($"Invalid access token result status '{result.Status ?? "(null)"}'"); + } + + if (parsedStatus == AccessTokenResultStatus.RequiresRedirect) + { + var redirectUrl = GetRedirectUrl(null); + result.RedirectUrl = redirectUrl.ToString(); + } + + return new AccessTokenResult(parsedStatus, result.Token, result.RedirectUrl); + } + + /// <inheritdoc /> + public virtual async ValueTask<AccessTokenResult> RequestAccessToken(AccessTokenRequestOptions options) + { + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } + + await EnsureAuthService(); + var result = await JsRuntime.InvokeAsync<InternalAccessTokenResult>("AuthenticationService.getAccessToken", options); + + if (!Enum.TryParse<AccessTokenResultStatus>(result.Status, ignoreCase: true, out var parsedStatus)) + { + throw new InvalidOperationException($"Invalid access token result status '{result.Status ?? "(null)"}'"); + } + + if (parsedStatus == AccessTokenResultStatus.RequiresRedirect) + { + var redirectUrl = GetRedirectUrl(options.ReturnUrl); + result.RedirectUrl = redirectUrl.ToString(); + } + + return new AccessTokenResult(parsedStatus, result.Token, result.RedirectUrl); + } + + private Uri GetRedirectUrl(string customReturnUrl) + { + var returnUrl = customReturnUrl != null ? Navigation.ToAbsoluteUri(customReturnUrl).ToString() : null; + var encodedReturnUrl = Uri.EscapeDataString(returnUrl ?? Navigation.Uri); + var redirectUrl = Navigation.ToAbsoluteUri($"{Options.AuthenticationPaths.LogInPath}?returnUrl={encodedReturnUrl}"); + return redirectUrl; + } + + private async Task<ClaimsPrincipal> GetUser(bool useCache = false) + { + var now = DateTimeOffset.Now; + if (useCache && now < _userLastCheck + _userCacheRefreshInterval) + { + return _cachedUser; + } + + _cachedUser = await GetAuthenticatedUser(); + _userLastCheck = now; + + return _cachedUser; + } + + /// <summary> + /// Gets the current authenticated used using JavaScript interop. + /// </summary> + /// <returns>A <see cref="Task{ClaimsPrincipal}"/>that will return the current authenticated user when completes.</returns> + protected internal virtual async ValueTask<ClaimsPrincipal> GetAuthenticatedUser() + { + await EnsureAuthService(); + var account = await JsRuntime.InvokeAsync<TAccount>("AuthenticationService.getUser"); + var user = await AccountClaimsPrincipalFactory.CreateUserAsync(account, Options.UserOptions); + + return user; + } + + private async ValueTask EnsureAuthService() + { + if (!_initialized) + { + await JsRuntime.InvokeVoidAsync("AuthenticationService.init", Options.ProviderOptions); + _initialized = true; + } + } + + private void UpdateUser(Task<ClaimsPrincipal> task) + { + NotifyAuthenticationStateChanged(UpdateAuthenticationState(task)); + + static async Task<AuthenticationState> UpdateAuthenticationState(Task<ClaimsPrincipal> futureUser) => new AuthenticationState(await futureUser); + } + } + + // Internal for testing purposes + internal struct InternalAccessTokenResult + { + public string Status { get; set; } + public AccessToken Token { get; set; } + public string RedirectUrl { get; set; } + } + + // Internal for testing purposes + internal struct InternalRemoteAuthenticationResult<TRemoteAuthenticationState> where TRemoteAuthenticationState : RemoteAuthenticationState + { + public string Status { get; set; } + + public string ErrorMessage { get; set; } + + public TRemoteAuthenticationState State { get; set; } + + public RemoteAuthenticationResult<TRemoteAuthenticationState> Convert() + { + var result = new RemoteAuthenticationResult<TRemoteAuthenticationState>(); + result.ErrorMessage = ErrorMessage; + result.State = State; + + if (Status != null && Enum.TryParse<RemoteAuthenticationStatus>(Status, ignoreCase: true, out var status)) + { + result.Status = status; + } + else + { + throw new InvalidOperationException($"Can't convert status '${Status ?? "(null)"}'."); + } + + return result; + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/SignOutSessionStateManager.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/SignOutSessionStateManager.cs new file mode 100644 index 0000000000000000000000000000000000000000..2867f2639ef18b065fb4310bfca11245b9fc09d0 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/SignOutSessionStateManager.cs @@ -0,0 +1,80 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.JSInterop; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + /// <summary> + /// Handles CSRF protection for the logout endpoint. + /// </summary> + public class SignOutSessionStateManager + { + private readonly IJSRuntime _jsRuntime; + private static readonly JsonSerializerOptions _serializationOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + }; + + public SignOutSessionStateManager(IJSRuntime jsRuntime) => _jsRuntime = jsRuntime; + + /// <summary> + /// Sets up some state in session storage to allow for logouts from within the <see cref="RemoteAuthenticationDefaults.LogoutPath"/> page. + /// </summary> + /// <returns>A <see cref="ValueTask"/> that completes when the state has been saved to session storage.</returns> + public virtual ValueTask SetSignOutState() + { + return _jsRuntime.InvokeVoidAsync( + "sessionStorage.setItem", + "Microsoft.AspNetCore.Components.WebAssembly.Authentication.SignOutState", + JsonSerializer.Serialize(SignOutState.Instance, _serializationOptions)); + } + + /// <summary> + /// Validates the existence of some state previously setup by <see cref="SetSignOutState"/> in session storage to allow + /// logouts from within the <see cref="RemoteAuthenticationDefaults.LogoutPath"/> page. + /// </summary> + /// <returns>A <see cref="Task"/> that completes when the state has been validated and indicates the validity of the state.</returns> + public virtual async Task<bool> ValidateSignOutState() + { + var state = await GetSignOutState(); + if (state.Local) + { + await ClearSignOutState(); + return true; + } + + return false; + } + + private async ValueTask<SignOutState> GetSignOutState() + { + var result = await _jsRuntime.InvokeAsync<string>( + "sessionStorage.getItem", + "Microsoft.AspNetCore.Components.WebAssembly.Authentication.SignOutState"); + if (result == null) + { + return default; + } + + return JsonSerializer.Deserialize<SignOutState>(result, _serializationOptions); + } + + private ValueTask ClearSignOutState() + { + return _jsRuntime.InvokeVoidAsync( + "sessionStorage.removeItem", + "Microsoft.AspNetCore.Components.WebAssembly.Authentication.SignOutState"); + } + + private struct SignOutState + { + public static readonly SignOutState Instance = new SignOutState { Local = true }; + + public bool Local { get; set; } + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/WebAssemblyAuthenticationServiceCollectionExtensions.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/WebAssemblyAuthenticationServiceCollectionExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..a3c76564d12f774c23d5c4ec2de02503d65c9673 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/WebAssemblyAuthenticationServiceCollectionExtensions.cs @@ -0,0 +1,214 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// <summary> + /// Contains extension methods to add authentication to Blazor WebAssembly applications. + /// </summary> + public static class WebAssemblyAuthenticationServiceCollectionExtensions + { + /// <summary> + /// Adds support for authentication for SPA applications using the given <typeparamref name="TProviderOptions"/> and + /// <typeparamref name="TRemoteAuthenticationState"/>. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The state to be persisted across authentication operations.</typeparam> + /// <typeparam name="TAccount">The account type.</typeparam> + /// <typeparam name="TProviderOptions">The configuration options of the underlying provider being used for handling the authentication operations.</typeparam> + /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> + /// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns> + public static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount> AddRemoteAuthentication<TRemoteAuthenticationState, TAccount, TProviderOptions>(this IServiceCollection services) + where TRemoteAuthenticationState : RemoteAuthenticationState + where TAccount : RemoteUserAccount + where TProviderOptions : class, new() + { + services.AddOptions(); + services.AddAuthorizationCore(); + services.TryAddScoped<AuthenticationStateProvider, RemoteAuthenticationService<TRemoteAuthenticationState, TAccount, TProviderOptions>>(); + services.TryAddScoped(sp => + { + return (IRemoteAuthenticationService<TRemoteAuthenticationState>)sp.GetRequiredService<AuthenticationStateProvider>(); + }); + + services.TryAddTransient<BaseAddressAuthorizationMessageHandler>(); + services.TryAddTransient<AuthorizationMessageHandler>(); + + services.TryAddScoped(sp => + { + return (IAccessTokenProvider)sp.GetRequiredService<AuthenticationStateProvider>(); + }); + + services.TryAddScoped<IRemoteAuthenticationPathsProvider, DefaultRemoteApplicationPathsProvider<TProviderOptions>>(); + services.TryAddScoped<IAccessTokenProviderAccessor, AccessTokenProviderAccessor>(); + services.TryAddScoped<SignOutSessionStateManager>(); + + services.TryAddScoped<AccountClaimsPrincipalFactory<TAccount>>(); + + return new RemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount>(services); + } + + /// <summary> + /// Adds support for authentication for SPA applications using the given <typeparamref name="TProviderOptions"/> and + /// <typeparamref name="TRemoteAuthenticationState"/>. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The state to be persisted across authentication operations.</typeparam> + /// <typeparam name="TAccount">The account type.</typeparam> + /// <typeparam name="TProviderOptions">The configuration options of the underlying provider being used for handling the authentication operations.</typeparam> + /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> + /// <param name="configure">An action that will configure the <see cref="RemoteAuthenticationOptions{TProviderOptions}"/>.</param> + /// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns> + public static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount> AddRemoteAuthentication<TRemoteAuthenticationState, TAccount, TProviderOptions>(this IServiceCollection services, Action<RemoteAuthenticationOptions<TProviderOptions>> configure) + where TRemoteAuthenticationState : RemoteAuthenticationState + where TAccount : RemoteUserAccount + where TProviderOptions : class, new() + { + services.AddRemoteAuthentication<TRemoteAuthenticationState, TAccount, TProviderOptions>(); + if (configure != null) + { + services.Configure(configure); + } + + return new RemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount>(services); + } + + /// <summary> + /// Adds support for authentication for SPA applications using <see cref="OidcProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>. + /// </summary> + /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> + /// <param name="configure">An action that will configure the <see cref="RemoteAuthenticationOptions{TProviderOptions}"/>.</param> + /// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns> + public static IRemoteAuthenticationBuilder<RemoteAuthenticationState, RemoteUserAccount> AddOidcAuthentication(this IServiceCollection services, Action<RemoteAuthenticationOptions<OidcProviderOptions>> configure) + { + return AddOidcAuthentication<RemoteAuthenticationState>(services, configure); + } + + /// <summary> + /// Adds support for authentication for SPA applications using <see cref="OidcProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam> + /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> + /// <param name="configure">An action that will configure the <see cref="RemoteAuthenticationOptions{TProviderOptions}"/>.</param> + /// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns> + public static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, RemoteUserAccount> AddOidcAuthentication<TRemoteAuthenticationState>(this IServiceCollection services, Action<RemoteAuthenticationOptions<OidcProviderOptions>> configure) + where TRemoteAuthenticationState : RemoteAuthenticationState, new() + { + return AddOidcAuthentication<TRemoteAuthenticationState, RemoteUserAccount>(services, configure); + } + + /// <summary> + /// Adds support for authentication for SPA applications using <see cref="OidcProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam> + /// <typeparam name="TAccount">The account type.</typeparam> + /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> + /// <param name="configure">An action that will configure the <see cref="RemoteAuthenticationOptions{TProviderOptions}"/>.</param> + /// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns> + public static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount> AddOidcAuthentication<TRemoteAuthenticationState, TAccount>(this IServiceCollection services, Action<RemoteAuthenticationOptions<OidcProviderOptions>> configure) + where TRemoteAuthenticationState : RemoteAuthenticationState, new() + where TAccount : RemoteUserAccount + { + services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<OidcProviderOptions>>, DefaultOidcOptionsConfiguration>()); + + return AddRemoteAuthentication<TRemoteAuthenticationState, TAccount, OidcProviderOptions>(services, configure); + } + + /// <summary> + /// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>. + /// </summary> + /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> + /// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns> + public static IRemoteAuthenticationBuilder<RemoteAuthenticationState, RemoteUserAccount> AddApiAuthorization(this IServiceCollection services) + { + return AddApiauthorizationCore<RemoteAuthenticationState, RemoteUserAccount>(services, configure: null, Assembly.GetCallingAssembly().GetName().Name); + } + + /// <summary> + /// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam> + /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> + /// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns> + public static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, RemoteUserAccount> AddApiAuthorization<TRemoteAuthenticationState>(this IServiceCollection services) + where TRemoteAuthenticationState : RemoteAuthenticationState, new() + { + return AddApiauthorizationCore<TRemoteAuthenticationState, RemoteUserAccount>(services, configure: null, Assembly.GetCallingAssembly().GetName().Name); + } + + /// <summary> + /// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam> + /// <typeparam name="TAccount">The account type.</typeparam> + /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> + /// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns> + public static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount> AddApiAuthorization<TRemoteAuthenticationState, TAccount>(this IServiceCollection services) + where TRemoteAuthenticationState : RemoteAuthenticationState, new() + where TAccount : RemoteUserAccount + { + return AddApiauthorizationCore<TRemoteAuthenticationState, TAccount>(services, configure: null, Assembly.GetCallingAssembly().GetName().Name); + } + + /// <summary> + /// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>. + /// </summary> + /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> + /// <param name="configure">An action that will configure the <see cref="RemoteAuthenticationOptions{ApiAuthorizationProviderOptions}"/>.</param> + /// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns> + public static IRemoteAuthenticationBuilder<RemoteAuthenticationState, RemoteUserAccount> AddApiAuthorization(this IServiceCollection services, Action<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> configure) + { + return AddApiauthorizationCore<RemoteAuthenticationState, RemoteUserAccount>(services, configure, Assembly.GetCallingAssembly().GetName().Name); + } + + /// <summary> + /// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam> + /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> + /// <param name="configure">An action that will configure the <see cref="RemoteAuthenticationOptions{ApiAuthorizationProviderOptions}"/>.</param> + /// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns> + public static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, RemoteUserAccount> AddApiAuthorization<TRemoteAuthenticationState>(this IServiceCollection services, Action<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> configure) + where TRemoteAuthenticationState : RemoteAuthenticationState, new() + { + return AddApiauthorizationCore<TRemoteAuthenticationState, RemoteUserAccount>(services, configure, Assembly.GetCallingAssembly().GetName().Name); + } + + /// <summary> + /// Adds support for authentication for SPA applications using <see cref="ApiAuthorizationProviderOptions"/> and the <see cref="RemoteAuthenticationState"/>. + /// </summary> + /// <typeparam name="TRemoteAuthenticationState">The type of the remote authentication state.</typeparam> + /// <typeparam name="TAccount">The account type.</typeparam> + /// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param> + /// <param name="configure">An action that will configure the <see cref="RemoteAuthenticationOptions{ApiAuthorizationProviderOptions}"/>.</param> + /// <returns>The <see cref="IServiceCollection"/> where the services were registered.</returns> + public static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount> AddApiAuthorization<TRemoteAuthenticationState, TAccount>(this IServiceCollection services, Action<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> configure) + where TRemoteAuthenticationState : RemoteAuthenticationState, new() + where TAccount : RemoteUserAccount + { + return AddApiauthorizationCore<TRemoteAuthenticationState, TAccount>(services, configure, Assembly.GetCallingAssembly().GetName().Name); + } + + private static IRemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount> AddApiauthorizationCore<TRemoteAuthenticationState, TAccount>( + IServiceCollection services, + Action<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> configure, + string inferredClientId) + where TRemoteAuthenticationState : RemoteAuthenticationState + where TAccount : RemoteUserAccount + { + services.TryAddEnumerable( + ServiceDescriptor.Singleton<IPostConfigureOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>, DefaultApiAuthorizationOptionsConfiguration>(_ => + new DefaultApiAuthorizationOptionsConfiguration(inferredClientId))); + + services.AddRemoteAuthentication<TRemoteAuthenticationState, TAccount, ApiAuthorizationProviderOptions>(configure); + + return new RemoteAuthenticationBuilder<TRemoteAuthenticationState, TAccount>(services); + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/AuthorizationMessageHandlerTests.cs b/src/Components/WebAssembly/WebAssembly.Authentication/test/AuthorizationMessageHandlerTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..768d45d8ec4f4d846c353ad0aa80ccf8519d32ba --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/AuthorizationMessageHandlerTests.cs @@ -0,0 +1,208 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + public class AuthorizationMessageHandlerTests + { + [Fact] + public async Task Throws_IfTheListOfAllowedUrlsIsNotConfigured() + { + // Arrange + var expectedMessage = "The 'AuthorizationMessageHandler' is not configured. " + + "Call 'ConfigureHandler' and provide a list of endpoint urls to attach the token to."; + + var tokenProvider = new Mock<IAccessTokenProvider>(); + + var handler = new AuthorizationMessageHandler(tokenProvider.Object, Mock.Of<NavigationManager>()); + // Act & Assert + + var exception = await Assert.ThrowsAsync<InvalidOperationException>( + () => new HttpClient(handler).GetAsync("https://www.example.com")); + + Assert.Equal(expectedMessage, exception.Message); + } + + [Fact] + public async Task DoesNotAttachTokenToRequest_IfNotPresentInListOfAllowedUrls() + { + // Arrange + var tokenProvider = new Mock<IAccessTokenProvider>(); + + var handler = new AuthorizationMessageHandler(tokenProvider.Object, Mock.Of<NavigationManager>()); + handler.ConfigureHandler(new[] { "https://localhost:5001" }); + + var response = new HttpResponseMessage(HttpStatusCode.OK); + handler.InnerHandler = new TestMessageHandler(response); + + // Act + _ = await new HttpClient(handler).GetAsync("https://www.example.com"); + + // Assert + tokenProvider.VerifyNoOtherCalls(); + } + + [Fact] + public async Task RequestsTokenWithDefaultScopes_WhenNoTokenIsAvailable() + { + // Arrange + var tokenProvider = new Mock<IAccessTokenProvider>(); + tokenProvider.Setup(tp => tp.RequestAccessToken()) + .Returns(new ValueTask<AccessTokenResult>(new AccessTokenResult(AccessTokenResultStatus.Success, + new AccessToken + { + Expires = DateTime.Now.AddHours(1), + GrantedScopes = new string[] { "All" }, + Value = "asdf" + }, + "https://www.example.com"))); + + var handler = new AuthorizationMessageHandler(tokenProvider.Object, Mock.Of<NavigationManager>()); + handler.ConfigureHandler(new[] { "https://localhost:5001" }); + + var response = new HttpResponseMessage(HttpStatusCode.OK); + handler.InnerHandler = new TestMessageHandler(response); + + // Act + _ = await new HttpClient(handler).GetAsync("https://localhost:5001/weather"); + + // Assert + Assert.Equal("asdf", response.RequestMessage.Headers.Authorization.Parameter); + } + + [Fact] + public async Task CachesExistingTokenWhenPossible() + { + // Arrange + var tokenProvider = new Mock<IAccessTokenProvider>(); + tokenProvider.Setup(tp => tp.RequestAccessToken()) + .Returns(new ValueTask<AccessTokenResult>(new AccessTokenResult(AccessTokenResultStatus.Success, + new AccessToken + { + Expires = DateTime.Now.AddHours(1), + GrantedScopes = new string[] { "All" }, + Value = "asdf" + }, + "https://www.example.com"))); + + var handler = new AuthorizationMessageHandler(tokenProvider.Object, Mock.Of<NavigationManager>()); + handler.ConfigureHandler(new[] { "https://localhost:5001" }); + + var response = new HttpResponseMessage(HttpStatusCode.OK); + handler.InnerHandler = new TestMessageHandler(response); + + // Act + _ = await new HttpClient(handler).GetAsync("https://localhost:5001/weather"); + response.RequestMessage = null; + + _ = await new HttpClient(handler).GetAsync("https://localhost:5001/weather"); + + // Assert + Assert.Single(tokenProvider.Invocations); + Assert.Equal("asdf", response.RequestMessage.Headers.Authorization.Parameter); + } + + [Fact] + public async Task RequestNewTokenWhenCurrentTokenIsAboutToExpire() + { + // Arrange + var tokenProvider = new Mock<IAccessTokenProvider>(); + tokenProvider.Setup(tp => tp.RequestAccessToken()) + .Returns(new ValueTask<AccessTokenResult>(new AccessTokenResult(AccessTokenResultStatus.Success, + new AccessToken + { + Expires = DateTime.Now.AddMinutes(3), + GrantedScopes = new string[] { "All" }, + Value = "asdf" + }, + "https://www.example.com"))); + + var handler = new AuthorizationMessageHandler(tokenProvider.Object, Mock.Of<NavigationManager>()); + handler.ConfigureHandler(new[] { "https://localhost:5001" }); + + var response = new HttpResponseMessage(HttpStatusCode.OK); + handler.InnerHandler = new TestMessageHandler(response); + + // Act + _ = await new HttpClient(handler).GetAsync("https://localhost:5001/weather"); + response.RequestMessage = null; + + _ = await new HttpClient(handler).GetAsync("https://localhost:5001/weather"); + + // Assert + Assert.Equal(2, tokenProvider.Invocations.Count); + } + + [Fact] + public async Task ThrowsWhenItCanNotProvisionANewToken() + { + // Arrange + var tokenProvider = new Mock<IAccessTokenProvider>(); + tokenProvider.Setup(tp => tp.RequestAccessToken()) + .Returns(new ValueTask<AccessTokenResult>(new AccessTokenResult(AccessTokenResultStatus.RequiresRedirect, + null, + "https://www.example.com"))); + + var handler = new AuthorizationMessageHandler(tokenProvider.Object, Mock.Of<NavigationManager>()); + handler.ConfigureHandler(new[] { "https://localhost:5001" }); + + var response = new HttpResponseMessage(HttpStatusCode.OK); + handler.InnerHandler = new TestMessageHandler(response); + + // Act & assert + var exception = await Assert.ThrowsAsync<AccessTokenNotAvailableException>(() => new HttpClient(handler).GetAsync("https://localhost:5001/weather")); + } + + [Fact] + public async Task UsesCustomScopesAndReturnUrlWhenProvided() + { + // Arrange + var tokenProvider = new Mock<IAccessTokenProvider>(); + tokenProvider.Setup(tp => tp.RequestAccessToken(It.IsAny<AccessTokenRequestOptions>())) + .Returns(new ValueTask<AccessTokenResult>(new AccessTokenResult(AccessTokenResultStatus.Success, + new AccessToken + { + Expires = DateTime.Now.AddMinutes(3), + GrantedScopes = new string[] { "All" }, + Value = "asdf" + }, + "https://www.example.com/return"))); + + var handler = new AuthorizationMessageHandler(tokenProvider.Object, Mock.Of<NavigationManager>()); + handler.ConfigureHandler( + new[] { "https://localhost:5001" }, + scopes: new[] { "example.read", "example.write" }, + returnUrl: "https://www.example.com/return"); + + var response = new HttpResponseMessage(HttpStatusCode.OK); + handler.InnerHandler = new TestMessageHandler(response); + + // Act + _ = await new HttpClient(handler).GetAsync("https://localhost:5001/weather"); + + // Assert + Assert.Equal(1, tokenProvider.Invocations.Count); + } + } + + internal class TestMessageHandler : HttpMessageHandler + { + private readonly HttpResponseMessage _response; + + public TestMessageHandler(HttpResponseMessage response) => _response = response; + + protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + _response.RequestMessage = request; + return Task.FromResult(_response); + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj b/src/Components/WebAssembly/WebAssembly.Authentication/test/Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj new file mode 100644 index 0000000000000000000000000000000000000000..f6a0faf5e39027da3e4e34f868e36c5a0fc79bfb --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj @@ -0,0 +1,20 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netcoreapp3.1</TargetFramework> + <NoWarn>$(NoWarn);BL0005;BL0006</NoWarn> + <!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. --> + <CompileUsingReferenceAssemblies>false</CompileUsingReferenceAssemblies> + </PropertyGroup> + + <ItemGroup> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly" /> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" /> + <Reference Include="Microsoft.CodeAnalysis.CSharp" /> + </ItemGroup> + + <ItemGroup> + <Compile Include="..\..\WebAssembly\test\TestWebAssemblyJSRuntimeInvoker.cs" Link="Shared\TestWebAssemblyJSRuntimeInvoker.cs" /> + </ItemGroup> + +</Project> diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..eca45e8913a71e982a78a95be92e67e5d7ab1211 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticationServiceTests.cs @@ -0,0 +1,601 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; +using Microsoft.Extensions.Options; +using Microsoft.JSInterop; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + public class RemoteAuthenticationServiceTests + { + [Fact] + public async Task RemoteAuthenticationService_SignIn_UpdatesUserOnSuccess() + { + // Arrange + var testJsRuntime = new TestJsRuntime(); + var options = CreateOptions(); + var runtime = new RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions>( + testJsRuntime, + options, + new TestNavigationManager(), + new AccountClaimsPrincipalFactory<RemoteUserAccount>(Mock.Of<IAccessTokenProviderAccessor>())); + + var state = new RemoteAuthenticationState(); + testJsRuntime.SignInResult = new InternalRemoteAuthenticationResult<RemoteAuthenticationState> + { + State = state, + Status = RemoteAuthenticationStatus.Success.ToString() + }; + + // Act + await runtime.SignInAsync(new RemoteAuthenticationContext<RemoteAuthenticationState> { State = state }); + + // Assert + Assert.Equal( + new[] { "AuthenticationService.init", "AuthenticationService.signIn", "AuthenticationService.getUser" }, + testJsRuntime.PastInvocations.Select(i => i.identifier).ToArray()); + } + + [Theory] + [InlineData(RemoteAuthenticationStatus.Redirect)] + [InlineData(RemoteAuthenticationStatus.Failure)] + [InlineData(RemoteAuthenticationStatus.OperationCompleted)] + public async Task RemoteAuthenticationService_SignIn_DoesNotUpdateUserOnOtherResult(RemoteAuthenticationStatus value) + { + // Arrange + var testJsRuntime = new TestJsRuntime(); + var options = CreateOptions(); + var runtime = new RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions>( + testJsRuntime, + options, + new TestNavigationManager(), + new AccountClaimsPrincipalFactory<RemoteUserAccount>(Mock.Of<IAccessTokenProviderAccessor>())); + + var state = new RemoteAuthenticationState(); + testJsRuntime.SignInResult = new InternalRemoteAuthenticationResult<RemoteAuthenticationState> + { + Status = value.ToString() + }; + + // Act + await runtime.SignInAsync(new RemoteAuthenticationContext<RemoteAuthenticationState> { State = state }); + + // Assert + Assert.Equal( + new[] { "AuthenticationService.init", "AuthenticationService.signIn" }, + testJsRuntime.PastInvocations.Select(i => i.identifier).ToArray()); + } + + [Fact] + public async Task RemoteAuthenticationService_CompleteSignInAsync_UpdatesUserOnSuccess() + { + // Arrange + var testJsRuntime = new TestJsRuntime(); + var options = CreateOptions(); + var runtime = new RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions>( + testJsRuntime, + options, + new TestNavigationManager(), + new AccountClaimsPrincipalFactory<RemoteUserAccount>(Mock.Of<IAccessTokenProviderAccessor>())); + + var state = new RemoteAuthenticationState(); + testJsRuntime.CompleteSignInResult = new InternalRemoteAuthenticationResult<RemoteAuthenticationState> + { + State = state, + Status = RemoteAuthenticationStatus.Success.ToString() + }; + + // Act + await runtime.CompleteSignInAsync(new RemoteAuthenticationContext<RemoteAuthenticationState> { Url = "https://www.example.com/base/login-callback" }); + + // Assert + Assert.Equal( + new[] { "AuthenticationService.init", "AuthenticationService.completeSignIn", "AuthenticationService.getUser" }, + testJsRuntime.PastInvocations.Select(i => i.identifier).ToArray()); + } + + [Theory] + [InlineData(RemoteAuthenticationStatus.Redirect)] + [InlineData(RemoteAuthenticationStatus.Failure)] + [InlineData(RemoteAuthenticationStatus.OperationCompleted)] + public async Task RemoteAuthenticationService_CompleteSignInAsync_DoesNotUpdateUserOnOtherResult(RemoteAuthenticationStatus value) + { + // Arrange + var testJsRuntime = new TestJsRuntime(); + var options = CreateOptions(); + var runtime = new RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions>( + testJsRuntime, + options, + new TestNavigationManager(), + new AccountClaimsPrincipalFactory<RemoteUserAccount>(Mock.Of<IAccessTokenProviderAccessor>())); + + var state = new RemoteAuthenticationState(); + testJsRuntime.CompleteSignInResult = new InternalRemoteAuthenticationResult<RemoteAuthenticationState> + { + Status = value.ToString().ToString() + }; + + // Act + await runtime.CompleteSignInAsync(new RemoteAuthenticationContext<RemoteAuthenticationState> { Url = "https://www.example.com/base/login-callback" }); + + // Assert + Assert.Equal( + new[] { "AuthenticationService.init", "AuthenticationService.completeSignIn" }, + testJsRuntime.PastInvocations.Select(i => i.identifier).ToArray()); + } + + [Fact] + public async Task RemoteAuthenticationService_SignOut_UpdatesUserOnSuccess() + { + // Arrange + var testJsRuntime = new TestJsRuntime(); + var options = CreateOptions(); + var runtime = new RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions>( + testJsRuntime, + options, + new TestNavigationManager(), + new AccountClaimsPrincipalFactory<RemoteUserAccount>(Mock.Of<IAccessTokenProviderAccessor>())); + + var state = new RemoteAuthenticationState(); + testJsRuntime.SignOutResult = new InternalRemoteAuthenticationResult<RemoteAuthenticationState> + { + State = state, + Status = RemoteAuthenticationStatus.Success.ToString() + }; + + // Act + await runtime.SignOutAsync(new RemoteAuthenticationContext<RemoteAuthenticationState> { State = state }); + + // Assert + Assert.Equal( + new[] { "AuthenticationService.init", "AuthenticationService.signOut", "AuthenticationService.getUser" }, + testJsRuntime.PastInvocations.Select(i => i.identifier).ToArray()); + } + + [Theory] + [InlineData(RemoteAuthenticationStatus.Redirect)] + [InlineData(RemoteAuthenticationStatus.Failure)] + [InlineData(RemoteAuthenticationStatus.OperationCompleted)] + public async Task RemoteAuthenticationService_SignOut_DoesNotUpdateUserOnOtherResult(RemoteAuthenticationStatus value) + { + // Arrange + var testJsRuntime = new TestJsRuntime(); + var options = CreateOptions(); + var runtime = new RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions>( + testJsRuntime, + options, + new TestNavigationManager(), + new AccountClaimsPrincipalFactory<RemoteUserAccount>(Mock.Of<IAccessTokenProviderAccessor>())); + + var state = new RemoteAuthenticationState(); + testJsRuntime.SignOutResult = new InternalRemoteAuthenticationResult<RemoteAuthenticationState> + { + Status = value.ToString() + }; + + // Act + await runtime.SignOutAsync(new RemoteAuthenticationContext<RemoteAuthenticationState> { State = state }); + + // Assert + Assert.Equal( + new[] { "AuthenticationService.init", "AuthenticationService.signOut" }, + testJsRuntime.PastInvocations.Select(i => i.identifier).ToArray()); + } + + [Fact] + public async Task RemoteAuthenticationService_CompleteSignOutAsync_UpdatesUserOnSuccess() + { + // Arrange + var testJsRuntime = new TestJsRuntime(); + var options = CreateOptions(); + var runtime = new RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions>( + testJsRuntime, + options, + new TestNavigationManager(), + new AccountClaimsPrincipalFactory<RemoteUserAccount>(Mock.Of<IAccessTokenProviderAccessor>())); + + var state = new RemoteAuthenticationState(); + testJsRuntime.CompleteSignOutResult = new InternalRemoteAuthenticationResult<RemoteAuthenticationState> + { + State = state, + Status = RemoteAuthenticationStatus.Success.ToString() + }; + + // Act + await runtime.CompleteSignOutAsync(new RemoteAuthenticationContext<RemoteAuthenticationState> { Url = "https://www.example.com/base/login-callback" }); + + // Assert + Assert.Equal( + new[] { "AuthenticationService.init", "AuthenticationService.completeSignOut", "AuthenticationService.getUser" }, + testJsRuntime.PastInvocations.Select(i => i.identifier).ToArray()); + } + + [Theory] + [InlineData(RemoteAuthenticationStatus.Redirect)] + [InlineData(RemoteAuthenticationStatus.Failure)] + [InlineData(RemoteAuthenticationStatus.OperationCompleted)] + public async Task RemoteAuthenticationService_CompleteSignOutAsync_DoesNotUpdateUserOnOtherResult(RemoteAuthenticationStatus value) + { + // Arrange + var testJsRuntime = new TestJsRuntime(); + var options = CreateOptions(); + var runtime = new RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions>( + testJsRuntime, + options, + new TestNavigationManager(), + new AccountClaimsPrincipalFactory<RemoteUserAccount>(Mock.Of<IAccessTokenProviderAccessor>())); + + var state = new RemoteAuthenticationState(); + testJsRuntime.CompleteSignOutResult = new InternalRemoteAuthenticationResult<RemoteAuthenticationState> + { + Status = value.ToString() + }; + + // Act + await runtime.CompleteSignOutAsync(new RemoteAuthenticationContext<RemoteAuthenticationState> { Url = "https://www.example.com/base/login-callback" }); + + // Assert + Assert.Equal( + new[] { "AuthenticationService.init", "AuthenticationService.completeSignOut" }, + testJsRuntime.PastInvocations.Select(i => i.identifier).ToArray()); + } + + [Fact] + public async Task RemoteAuthenticationService_GetAccessToken_ReturnsAccessTokenResult() + { + // Arrange + var testJsRuntime = new TestJsRuntime(); + var options = CreateOptions(); + var runtime = new RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions>( + testJsRuntime, + options, + new TestNavigationManager(), + new AccountClaimsPrincipalFactory<RemoteUserAccount>(Mock.Of<IAccessTokenProviderAccessor>())); + + var state = new RemoteAuthenticationState(); + testJsRuntime.GetAccessTokenResult = new InternalAccessTokenResult + { + Status = "success", + Token = new AccessToken + { + Value = "1234", + GrantedScopes = new[] { "All" }, + Expires = new DateTimeOffset(2050, 5, 13, 0, 0, 0, TimeSpan.Zero) + } + }; + + // Act + var result = await runtime.RequestAccessToken(); + + // Assert + Assert.Equal( + new[] { "AuthenticationService.init", "AuthenticationService.getAccessToken" }, + testJsRuntime.PastInvocations.Select(i => i.identifier).ToArray()); + + Assert.True(result.TryGetToken(out var token)); + Assert.Equal(result.Status, Enum.Parse<AccessTokenResultStatus>(testJsRuntime.GetAccessTokenResult.Status, ignoreCase: true)); + Assert.Equal(result.RedirectUrl, testJsRuntime.GetAccessTokenResult.RedirectUrl); + Assert.Equal(token, testJsRuntime.GetAccessTokenResult.Token); + } + + [Fact] + public async Task RemoteAuthenticationService_GetAccessToken_PassesDownOptions() + { + // Arrange + var testJsRuntime = new TestJsRuntime(); + var options = CreateOptions(); + var runtime = new RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions>( + testJsRuntime, + options, + new TestNavigationManager(), + new AccountClaimsPrincipalFactory<RemoteUserAccount>(Mock.Of<IAccessTokenProviderAccessor>())); + + var state = new RemoteAuthenticationState(); + testJsRuntime.GetAccessTokenResult = new InternalAccessTokenResult + { + Status = "requiresRedirect", + }; + + var tokenOptions = new AccessTokenRequestOptions + { + Scopes = new[] { "something" } + }; + + var expectedRedirectUrl = "https://www.example.com/base/login?returnUrl=https%3A%2F%2Fwww.example.com%2Fbase%2Fadd-product"; + + // Act + var result = await runtime.RequestAccessToken(tokenOptions); + + // Assert + Assert.Equal( + new[] { "AuthenticationService.init", "AuthenticationService.getAccessToken" }, + testJsRuntime.PastInvocations.Select(i => i.identifier).ToArray()); + + Assert.False(result.TryGetToken(out var token)); + Assert.Null(token); + Assert.Equal(result.Status, Enum.Parse<AccessTokenResultStatus>(testJsRuntime.GetAccessTokenResult.Status, ignoreCase: true)); + Assert.Equal(expectedRedirectUrl, result.RedirectUrl); + Assert.Equal(tokenOptions, (AccessTokenRequestOptions)testJsRuntime.PastInvocations[^1].args[0]); + } + + [Fact] + public async Task RemoteAuthenticationService_GetAccessToken_ComputesDefaultReturnUrlOnRequiresRedirect() + { + // Arrange + var testJsRuntime = new TestJsRuntime(); + var options = CreateOptions(); + var runtime = new RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions>( + testJsRuntime, + options, + new TestNavigationManager(), + new AccountClaimsPrincipalFactory<RemoteUserAccount>(Mock.Of<IAccessTokenProviderAccessor>())); + + var state = new RemoteAuthenticationState(); + testJsRuntime.GetAccessTokenResult = new InternalAccessTokenResult + { + Status = "requiresRedirect", + }; + + var tokenOptions = new AccessTokenRequestOptions + { + Scopes = new[] { "something" }, + ReturnUrl = "https://www.example.com/base/add-saved-product/123413241234" + }; + + var expectedRedirectUrl = "https://www.example.com/base/login?returnUrl=https%3A%2F%2Fwww.example.com%2Fbase%2Fadd-saved-product%2F123413241234"; + + // Act + var result = await runtime.RequestAccessToken(tokenOptions); + + // Assert + Assert.Equal( + new[] { "AuthenticationService.init", "AuthenticationService.getAccessToken" }, + testJsRuntime.PastInvocations.Select(i => i.identifier).ToArray()); + + Assert.False(result.TryGetToken(out var token)); + Assert.Null(token); + Assert.Equal(result.Status, Enum.Parse<AccessTokenResultStatus>(testJsRuntime.GetAccessTokenResult.Status, ignoreCase: true)); + Assert.Equal(expectedRedirectUrl, result.RedirectUrl); + Assert.Equal(tokenOptions, (AccessTokenRequestOptions)testJsRuntime.PastInvocations[^1].args[0]); + } + + [Fact] + public async Task RemoteAuthenticationService_GetUser_ReturnsAnonymousClaimsPrincipal_ForUnauthenticatedUsers() + { + // Arrange + var testJsRuntime = new TestJsRuntime(); + var options = CreateOptions(); + var runtime = new RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions>( + testJsRuntime, + options, + new TestNavigationManager(), + new AccountClaimsPrincipalFactory<RemoteUserAccount>(Mock.Of<IAccessTokenProviderAccessor>())); + + testJsRuntime.GetUserResult = default; + + // Act + var result = await runtime.GetAuthenticatedUser(); + + // Assert + Assert.Empty(result.Claims); + Assert.Single(result.Identities); + Assert.False(result.Identity.IsAuthenticated); + + Assert.Equal( + new[] { "AuthenticationService.init", "AuthenticationService.getUser" }, + testJsRuntime.PastInvocations.Select(i => i.identifier).ToArray()); + } + + [Fact] + public async Task RemoteAuthenticationService_GetUser_ReturnsUser_ForAuthenticatedUsers() + { + // Arrange + var testJsRuntime = new TestJsRuntime(); + var options = CreateOptions(); + var runtime = new RemoteAuthenticationService<RemoteAuthenticationState, CoolRoleAccount, OidcProviderOptions>( + testJsRuntime, + options, + new TestNavigationManager(), + new TestAccountClaimsPrincipalFactory(Mock.Of<IAccessTokenProviderAccessor>())); + + var account = new CoolRoleAccount + { + CoolRole = new[] { "admin", "cool", "fantastic" }, + AdditionalProperties = new Dictionary<string, object> + { + ["CoolName"] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize("Alfred")) + } + }; + + testJsRuntime.GetUserResult = account; + + // Act + var result = await runtime.GetAuthenticatedUser(); + + // Assert + Assert.Single(result.Identities); + Assert.True(result.Identity.IsAuthenticated); + Assert.Equal("Alfred", result.Identity.Name); + Assert.Equal("a", result.Identity.AuthenticationType); + Assert.True(result.IsInRole("admin")); + Assert.True(result.IsInRole("cool")); + Assert.True(result.IsInRole("fantastic")); + } + + [Fact] + public async Task RemoteAuthenticationService_GetUser_DoesNotMapScopesToRoles() + { + // Arrange + var testJsRuntime = new TestJsRuntime(); + var options = CreateOptions("scope"); + var runtime = new RemoteAuthenticationService<RemoteAuthenticationState, CoolRoleAccount, OidcProviderOptions>( + testJsRuntime, + options, + new TestNavigationManager(), + new TestAccountClaimsPrincipalFactory(Mock.Of<IAccessTokenProviderAccessor>())); + + var account = new CoolRoleAccount + { + CoolRole = new[] { "admin", "cool", "fantastic" }, + AdditionalProperties = new Dictionary<string, object> + { + ["CoolName"] = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize("Alfred")), + } + }; + + testJsRuntime.GetUserResult = account; + testJsRuntime.GetAccessTokenResult = new InternalAccessTokenResult + { + Status = "success", + Token = new AccessToken + { + Value = "1234", + GrantedScopes = new[] { "All" }, + Expires = new DateTimeOffset(2050, 5, 13, 0, 0, 0, TimeSpan.Zero) + } + }; + + // Act + var result = await runtime.GetAuthenticatedUser(); + + // Assert + Assert.Single(result.Identities); + Assert.True(result.Identity.IsAuthenticated); + Assert.Equal("Alfred", result.Identity.Name); + Assert.Equal("a", result.Identity.AuthenticationType); + Assert.True(result.IsInRole("admin")); + Assert.True(result.IsInRole("cool")); + Assert.True(result.IsInRole("fantastic")); + Assert.Empty(result.FindAll("scope")); + } + + private static IOptions<RemoteAuthenticationOptions<OidcProviderOptions>> CreateOptions(string scopeClaim = null) + { + var options = new RemoteAuthenticationOptions<OidcProviderOptions>(); + + options.AuthenticationPaths.LogInPath = "login"; + options.AuthenticationPaths.LogInCallbackPath = "a"; + options.AuthenticationPaths.LogInFailedPath = "a"; + options.AuthenticationPaths.RegisterPath = "a"; + options.AuthenticationPaths.ProfilePath = "a"; + options.AuthenticationPaths.RemoteRegisterPath = "a"; + options.AuthenticationPaths.RemoteProfilePath = "a"; + options.AuthenticationPaths.LogOutPath = "a"; + options.AuthenticationPaths.LogOutCallbackPath = "a"; + options.AuthenticationPaths.LogOutFailedPath = "a"; + options.AuthenticationPaths.LogOutSucceededPath = "a"; + options.UserOptions.AuthenticationType = "a"; + options.UserOptions.ScopeClaim = scopeClaim; + options.UserOptions.RoleClaim = "coolRole"; + options.UserOptions.NameClaim = "coolName"; + options.ProviderOptions.Authority = "a"; + options.ProviderOptions.ClientId = "a"; + options.ProviderOptions.DefaultScopes.Add("openid"); + options.ProviderOptions.RedirectUri = "https://www.example.com/base/custom-login"; + options.ProviderOptions.PostLogoutRedirectUri = "https://www.example.com/base/custom-logout"; + + return Options.Create(options); + } + + private class TestJsRuntime : IJSRuntime + { + public IList<(string identifier, object[] args)> PastInvocations { get; set; } = new List<(string, object[])>(); + + public InternalRemoteAuthenticationResult<RemoteAuthenticationState> SignInResult { get; set; } + + public InternalRemoteAuthenticationResult<RemoteAuthenticationState> CompleteSignInResult { get; set; } + + public InternalRemoteAuthenticationResult<RemoteAuthenticationState> SignOutResult { get; set; } + + public InternalRemoteAuthenticationResult<RemoteAuthenticationState> CompleteSignOutResult { get; set; } + + public InternalAccessTokenResult GetAccessTokenResult { get; set; } + + public RemoteUserAccount GetUserResult { get; set; } + + public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args) + { + PastInvocations.Add((identifier, args)); + return new ValueTask<TValue>((TValue)GetInvocationResult(identifier)); + } + + public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object[] args) + { + PastInvocations.Add((identifier, args)); + return new ValueTask<TValue>((TValue)GetInvocationResult(identifier)); + } + + private object GetInvocationResult(string identifier) + { + switch (identifier) + { + case "AuthenticationService.init": + return default; + case "AuthenticationService.signIn": + return SignInResult; + case "AuthenticationService.completeSignIn": + return CompleteSignInResult; + case "AuthenticationService.signOut": + return SignOutResult; + case "AuthenticationService.completeSignOut": + return CompleteSignOutResult; + case "AuthenticationService.getAccessToken": + return GetAccessTokenResult; + case "AuthenticationService.getUser": + return GetUserResult; + default: + break; + } + + return default; + } + } + } + + internal class TestAccountClaimsPrincipalFactory : AccountClaimsPrincipalFactory<CoolRoleAccount> + { + public TestAccountClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor) : base(accessor) + { + } + + public override async ValueTask<ClaimsPrincipal> CreateUserAsync( + CoolRoleAccount account, + RemoteAuthenticationUserOptions options) + { + var user = await base.CreateUserAsync(account, options); + + if (account.CoolRole != null) + { + foreach (var role in account.CoolRole) + { + ((ClaimsIdentity)user.Identity).AddClaim(new Claim("CoolRole", role)); + } + } + + return user; + } + } + + internal class CoolRoleAccount : RemoteUserAccount + { + public string[] CoolRole { get; set; } + } + + internal class TestNavigationManager : NavigationManager + { + public TestNavigationManager() => + Initialize("https://www.example.com/base/", "https://www.example.com/base/add-product"); + + protected override void NavigateToCore(string uri, bool forceLoad) => throw new NotImplementedException(); + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..54649ad1292a019b4d319854ce2bd75665bdd4c0 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/RemoteAuthenticatorCoreTests.cs @@ -0,0 +1,709 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Runtime.ExceptionServices; +using System.Security.Claims; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.RenderTree; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Microsoft.JSInterop; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + public class RemoteAuthenticatorCoreTests + { + private const string _action = nameof(RemoteAuthenticatorViewCore<RemoteAuthenticationState>.Action); + private const string _onLogInSucceded = nameof(RemoteAuthenticatorViewCore<RemoteAuthenticationState>.OnLogInSucceeded); + private const string _onLogOutSucceeded = nameof(RemoteAuthenticatorViewCore<RemoteAuthenticationState>.OnLogOutSucceeded); + + [Fact] + public async Task AuthenticationManager_Throws_ForInvalidAction() + { + // Arrange + var remoteAuthenticator = new RemoteAuthenticatorViewCore<RemoteAuthenticationState>(); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = "" + }); + + // Act & assert + await Assert.ThrowsAsync<InvalidOperationException>(() => remoteAuthenticator.SetParametersAsync(parameters)); + } + + [Fact] + public async Task AuthenticationManager_Login_NavigatesToReturnUrlOnSuccess() + { + // Arrange + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager( + "https://www.example.com/base/authentication/login?returnUrl=https://www.example.com/base/fetchData"); + + authServiceMock.SignInCallback = _ => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.Success, + State = remoteAuthenticator.AuthenticationState + }); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogIn + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters)); + + // Assert + Assert.Equal("https://www.example.com/base/fetchData", jsRuntime.LastInvocation.args[0]); + } + + [Fact] + public async Task AuthenticationManager_Login_DoesNothingOnRedirect() + { + // Arrange + var originalUrl = "https://www.example.com/base/authentication/login?returnUrl=https://www.example.com/base/fetchData"; + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager(originalUrl); + + authServiceMock.SignInCallback = s => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.Redirect, + State = remoteAuthenticator.AuthenticationState + }); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogIn + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters)); + + // Assert + Assert.Equal(originalUrl, remoteAuthenticator.Navigation.Uri); + + } + + [Fact] + public async Task AuthenticationManager_Login_NavigatesToLoginFailureOnError() + { + // Arrange + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager( + "https://www.example.com/base/authentication/login?returnUrl=https://www.example.com/base/fetchData"); + + authServiceMock.SignInCallback = s => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.Failure, + ErrorMessage = "There was an error trying to log in" + }); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogIn + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters)); + + // Assert + Assert.Equal("https://www.example.com/base/authentication/login-failed", remoteAuthenticator.Navigation.Uri.ToString()); + } + + [Fact] + public async Task AuthenticationManager_LoginCallback_ThrowsOnRedirectResult() + { + // Arrange + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager( + "https://www.example.com/base/authentication/login?returnUrl=https://www.example.com/base/fetchData"); + + authServiceMock.CompleteSignInCallback = s => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.Redirect + }); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogInCallback + }); + + await Assert.ThrowsAsync<InvalidOperationException>( + async () => await renderer.Dispatcher.InvokeAsync<object>(async () => + { + await remoteAuthenticator.SetParametersAsync(parameters); + return null; + })); + } + + [Fact] + public async Task AuthenticationManager_LoginCallback_DoesNothingOnOperationCompleted() + { + // Arrange + var originalUrl = "https://www.example.com/base/authentication/login-callback?code=1234"; + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager( + originalUrl); + + authServiceMock.CompleteSignInCallback = s => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.OperationCompleted + }); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogInCallback + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters)); + + // Assert + Assert.Equal(originalUrl, remoteAuthenticator.Navigation.Uri); + + } + + [Fact] + public async Task AuthenticationManager_LoginCallback_NavigatesToReturnUrlFromStateOnSuccess() + { + // Arrange + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager( + "https://www.example.com/base/authentication/login-callback?code=1234"); + + var fetchDataUrl = "https://www.example.com/base/fetchData"; + remoteAuthenticator.AuthenticationState.ReturnUrl = fetchDataUrl; + + authServiceMock.CompleteSignInCallback = s => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.Success, + State = remoteAuthenticator.AuthenticationState + }); + + var loggingSucceededCalled = false; + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogInCallback, + [_onLogInSucceded] = new EventCallbackFactory().Create< RemoteAuthenticationState>( + remoteAuthenticator, + (state) => loggingSucceededCalled = true), + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters)); + + // Assert + Assert.Equal(fetchDataUrl, jsRuntime.LastInvocation.args[0]); + Assert.True(loggingSucceededCalled); + + } + + [Fact] + public async Task AuthenticationManager_LoginCallback_NavigatesToLoginFailureOnError() + { + // Arrange + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager( + "https://www.example.com/base/authentication/login-callback?code=1234"); + + var fetchDataUrl = "https://www.example.com/base/fetchData"; + remoteAuthenticator.AuthenticationState.ReturnUrl = fetchDataUrl; + + authServiceMock.CompleteSignInCallback = s => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.Failure, + ErrorMessage = "There was an error trying to log in" + }); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogInCallback + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters)); + + // Assert + Assert.Equal( + "https://www.example.com/base/authentication/login-failed?message=There was an error trying to log in", + jsRuntime.LastInvocation.args[0]); + + } + + [Fact] + public async Task AuthenticationManager_Logout_NavigatesToReturnUrlOnSuccess() + { + // Arrange + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager( + "https://www.example.com/base/authentication/logout?returnUrl=https://www.example.com/base/"); + + authServiceMock.GetAuthenticatedUserCallback = () => new ValueTask<ClaimsPrincipal>(new ClaimsPrincipal(new ClaimsIdentity("Test"))); + + authServiceMock.SignOutCallback = s => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.Success, + State = remoteAuthenticator.AuthenticationState + }); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogOut + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters)); + + // Assert + Assert.Equal("https://www.example.com/base/", jsRuntime.LastInvocation.args[0]); + } + + [Fact] + public async Task AuthenticationManager_Logout_NavigatesToDefaultReturnUrlWhenNoReturnUrlIsPresent() + { + // Arrange + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager( + "https://www.example.com/base/authentication/logout"); + + authServiceMock.GetAuthenticatedUserCallback = () => new ValueTask<ClaimsPrincipal>(new ClaimsPrincipal(new ClaimsIdentity("Test"))); + + authServiceMock.SignOutCallback = s => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.Success, + State = remoteAuthenticator.AuthenticationState + }); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogOut + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters)); + + // Assert + Assert.Equal("https://www.example.com/base/authentication/logged-out", jsRuntime.LastInvocation.args[0]); + } + + [Fact] + public async Task AuthenticationManager_Logout_DoesNothingOnRedirect() + { + // Arrange + var originalUrl = "https://www.example.com/base/authentication/login?returnUrl=https://www.example.com/base/fetchData"; + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager(originalUrl); + + authServiceMock.GetAuthenticatedUserCallback = () => new ValueTask<ClaimsPrincipal>(new ClaimsPrincipal(new ClaimsIdentity("Test"))); + + authServiceMock.SignOutCallback = s => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.Redirect, + State = remoteAuthenticator.AuthenticationState + }); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogOut + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters)); + + // Assert + Assert.Equal(originalUrl, remoteAuthenticator.Navigation.Uri); + + } + + [Fact] + public async Task AuthenticationManager_Logout_RedirectsToFailureOnInvalidSignOutState() + { + // Arrange + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager( + "https://www.example.com/base/authentication/logout?returnUrl=https://www.example.com/base/fetchData"); + + if(remoteAuthenticator.SignOutManager is TestSignOutSessionStateManager testManager) + { + testManager.SignOutState = false; + } + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogOut + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters)); + + // Assert + Assert.Equal( + "https://www.example.com/base/authentication/logout-failed?message=The%20logout%20was%20not%20initiated%20from%20within%20the%20page.", + remoteAuthenticator.Navigation.Uri); + } + + [Fact] + public async Task AuthenticationManager_Logout_NavigatesToLogoutFailureOnError() + { + // Arrange + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager( + "https://www.example.com/base/authentication/logout?returnUrl=https://www.example.com/base/fetchData"); + + authServiceMock.GetAuthenticatedUserCallback = () => new ValueTask<ClaimsPrincipal>(new ClaimsPrincipal(new ClaimsIdentity("Test"))); + + authServiceMock.SignOutCallback = s => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.Failure, + ErrorMessage = "There was an error trying to log out" + }); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogOut + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters)); + + // Assert + Assert.Equal("https://www.example.com/base/authentication/logout-failed", remoteAuthenticator.Navigation.Uri.ToString()); + } + + [Fact] + public async Task AuthenticationManager_LogoutCallback_ThrowsOnRedirectResult() + { + // Arrange + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager( + "https://www.example.com/base/authentication/logout-callback?returnUrl=https://www.example.com/base/fetchData"); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogOutCallback + }); + + authServiceMock.CompleteSignOutCallback = s => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.Redirect, + }); + + + await Assert.ThrowsAsync<InvalidOperationException>( + async () => await renderer.Dispatcher.InvokeAsync<object>(async () => + { + await remoteAuthenticator.SetParametersAsync(parameters); + return null; + })); + } + + [Fact] + public async Task AuthenticationManager_LogoutCallback_DoesNothingOnOperationCompleted() + { + // Arrange + var originalUrl = "https://www.example.com/base/authentication/logout-callback?code=1234"; + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager( + originalUrl); + + authServiceMock.CompleteSignOutCallback = s => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.OperationCompleted + }); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogOutCallback + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters)); + + // Assert + Assert.Equal(originalUrl, remoteAuthenticator.Navigation.Uri); + } + + [Fact] + public async Task AuthenticationManager_LogoutCallback_NavigatesToReturnUrlFromStateOnSuccess() + { + // Arrange + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager( + "https://www.example.com/base/authentication/logout-callback-callback?code=1234"); + + var fetchDataUrl = "https://www.example.com/base/fetchData"; + remoteAuthenticator.AuthenticationState.ReturnUrl = fetchDataUrl; + + authServiceMock.CompleteSignOutCallback = s => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.Success, + State = remoteAuthenticator.AuthenticationState + }); + + var loggingOutSucceededCalled = false; + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogOutCallback, + [_onLogOutSucceeded] = new EventCallbackFactory().Create<RemoteAuthenticationState>( + remoteAuthenticator, + (state) => loggingOutSucceededCalled = true), + + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters)); + + // Assert + Assert.Equal(fetchDataUrl, jsRuntime.LastInvocation.args[0]); + Assert.True(loggingOutSucceededCalled); + + } + + [Fact] + public async Task AuthenticationManager_LogoutCallback_NavigatesToLoginFailureOnError() + { + // Arrange + var (remoteAuthenticator, renderer, authServiceMock, jsRuntime) = CreateAuthenticationManager( + "https://www.example.com/base/authentication/logout-callback?code=1234"); + + var fetchDataUrl = "https://www.example.com/base/fetchData"; + remoteAuthenticator.AuthenticationState.ReturnUrl = fetchDataUrl; + + authServiceMock.CompleteSignOutCallback = s => Task.FromResult(new RemoteAuthenticationResult<RemoteAuthenticationState>() + { + Status = RemoteAuthenticationStatus.Failure, + ErrorMessage = "There was an error trying to log out" + }); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = RemoteAuthenticationActions.LogOutCallback + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => remoteAuthenticator.SetParametersAsync(parameters)); + + // Assert + Assert.Equal( + "https://www.example.com/base/authentication/logout-failed?message=There was an error trying to log out", + jsRuntime.LastInvocation.args[0]); + + } + + public static TheoryData<UIValidator> DisplaysRightUIData { get; } = new TheoryData<UIValidator> + { + { new UIValidator { + Action = "login", SetupAction = (validator, remoteAuthenticator) => { remoteAuthenticator.LoggingIn = validator.Render; } } + }, + { new UIValidator { + Action = "login-callback", SetupAction = (validator, remoteAuthenticator) => { remoteAuthenticator.CompletingLoggingIn = validator.Render; } } + }, + { new UIValidator { + Action = "login-failed", SetupAction = (validator, remoteAuthenticator) => { remoteAuthenticator.LogInFailed = m => builder => validator.Render(builder); } } + }, + { new UIValidator { + Action = "profile", SetupAction = (validator, remoteAuthenticator) => { remoteAuthenticator.LoggingIn = validator.Render; } } + }, + // Profile fragment overrides + { new UIValidator { + Action = "profile", SetupAction = (validator, remoteAuthenticator) => { remoteAuthenticator.UserProfile = validator.Render; } } + }, + { new UIValidator { + Action = "register", SetupAction = (validator, remoteAuthenticator) => { remoteAuthenticator.LoggingIn = validator.Render; } } + }, + // Register fragment overrides + { new UIValidator { + Action = "register", SetupAction = (validator, remoteAuthenticator) => { remoteAuthenticator.Registering = validator.Render; } } + }, + { new UIValidator { + Action = "logout", SetupAction = (validator, remoteAuthenticator) => { remoteAuthenticator.LogOut = validator.Render; } } + }, + { new UIValidator { + Action = "logout-callback", SetupAction = (validator, remoteAuthenticator) => { remoteAuthenticator.CompletingLogOut = validator.Render; } } + }, + { new UIValidator { + Action = "logout-failed", SetupAction = (validator, remoteAuthenticator) => { remoteAuthenticator.LogOutFailed = m => builder => validator.Render(builder); } } + }, + { new UIValidator { + Action = "logged-out", SetupAction = (validator, remoteAuthenticator) => { remoteAuthenticator.LogOutSucceeded = validator.Render; } } + }, + }; + + [Theory] + [MemberData(nameof(DisplaysRightUIData))] + public async Task AuthenticationManager_DisplaysRightUI_ForEachStateAsync(UIValidator validator) + { + // Arrange + var renderer = new TestRenderer(new ServiceCollection().BuildServiceProvider()); + var authenticator = new TestRemoteAuthenticatorView(); + renderer.Attach(authenticator); + validator.Setup(authenticator); + + var parameters = ParameterView.FromDictionary(new Dictionary<string, object> + { + [_action] = validator.Action + }); + + // Act + await renderer.Dispatcher.InvokeAsync<object>(() => authenticator.SetParametersAsync(parameters)); + + // Assert + Assert.True(validator.WasCalled); + } + + public class UIValidator + { + public string Action { get; set; } + public Action<UIValidator, RemoteAuthenticatorViewCore<RemoteAuthenticationState>> SetupAction { get; set; } + public bool WasCalled { get; set; } + public RenderFragment Render { get; set; } + + public UIValidator() => Render = builder => WasCalled = true; + + internal void Setup(TestRemoteAuthenticatorView manager) => SetupAction(this, manager); + } + + private static + (RemoteAuthenticatorViewCore<RemoteAuthenticationState> manager, + TestRenderer renderer, + TestRemoteAuthenticationService authenticationServiceMock, + TestJsRuntime js) + + CreateAuthenticationManager( + string currentUri, + string baseUri = "https://www.example.com/base/") + { + var renderer = new TestRenderer(new ServiceCollection().BuildServiceProvider()); + var remoteAuthenticator = new RemoteAuthenticatorViewCore<RemoteAuthenticationState>(); + renderer.Attach(remoteAuthenticator); + + var navigationManager = new TestNavigationManager( + baseUri, + currentUri); + remoteAuthenticator.Navigation = navigationManager; + + remoteAuthenticator.AuthenticationState = new RemoteAuthenticationState(); + remoteAuthenticator.ApplicationPaths = new RemoteAuthenticationApplicationPathsOptions(); + + var jsRuntime = new TestJsRuntime(); + var authenticationServiceMock = new TestRemoteAuthenticationService( + jsRuntime, + Mock.Of<IOptions<RemoteAuthenticationOptions<OidcProviderOptions>>>(), + navigationManager); + + remoteAuthenticator.SignOutManager = new TestSignOutSessionStateManager(); + + remoteAuthenticator.AuthenticationService = authenticationServiceMock; + remoteAuthenticator.AuthenticationProvider = authenticationServiceMock; + remoteAuthenticator.JS = jsRuntime; + return (remoteAuthenticator, renderer, authenticationServiceMock, jsRuntime); + } + + private class TestNavigationManager : NavigationManager + { + public TestNavigationManager(string baseUrl, string currentUrl) => Initialize(baseUrl, currentUrl); + + protected override void NavigateToCore(string uri, bool forceLoad) + => Uri = System.Uri.IsWellFormedUriString(uri, UriKind.Absolute) ? uri : new Uri(new Uri(BaseUri), uri).ToString(); + } + + private class TestSignOutSessionStateManager : SignOutSessionStateManager + { + public TestSignOutSessionStateManager() : base(null) + { + } + + public bool SignOutState { get; set; } = true; + + public override ValueTask SetSignOutState() + { + SignOutState = true; + return default; + } + + public override Task<bool> ValidateSignOutState() => Task.FromResult(SignOutState); + } + + private class TestJsRuntime : IJSRuntime + { + public (string identifier, object[] args) LastInvocation { get; set; } + public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args) + { + LastInvocation = (identifier, args); + return default; + } + + public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object[] args) + { + LastInvocation = (identifier, args); + return default; + } + } + + public class TestRemoteAuthenticatorView : RemoteAuthenticatorViewCore<RemoteAuthenticationState> + { + public TestRemoteAuthenticatorView() + { + ApplicationPaths = new RemoteAuthenticationApplicationPathsOptions() + { + RemoteProfilePath = "Identity/Account/Manage", + RemoteRegisterPath = "Identity/Account/Register", + }; + } + + protected override Task OnParametersSetAsync() + { + if (Action == "register" || Action == "profile") + { + return base.OnParametersSetAsync(); + } + + return Task.CompletedTask; + } + } + + private class TestRemoteAuthenticationService : RemoteAuthenticationService<RemoteAuthenticationState, RemoteUserAccount, OidcProviderOptions> + { + public TestRemoteAuthenticationService( + IJSRuntime jsRuntime, + IOptions<RemoteAuthenticationOptions<OidcProviderOptions>> options, + TestNavigationManager navigationManager) : + base(jsRuntime, options, navigationManager, new AccountClaimsPrincipalFactory<RemoteUserAccount>(Mock.Of<IAccessTokenProviderAccessor>())) + { + } + + public Func<RemoteAuthenticationContext<RemoteAuthenticationState>, Task<RemoteAuthenticationResult<RemoteAuthenticationState>>> SignInCallback { get; set; } + public Func<RemoteAuthenticationContext<RemoteAuthenticationState>, Task<RemoteAuthenticationResult<RemoteAuthenticationState>>> CompleteSignInCallback { get; set; } + public Func<RemoteAuthenticationContext<RemoteAuthenticationState>, Task<RemoteAuthenticationResult<RemoteAuthenticationState>>> SignOutCallback { get; set; } + public Func<RemoteAuthenticationContext<RemoteAuthenticationState>, Task<RemoteAuthenticationResult<RemoteAuthenticationState>>> CompleteSignOutCallback { get; set; } + public Func<ValueTask<ClaimsPrincipal>> GetAuthenticatedUserCallback { get; set; } + + public async override Task<AuthenticationState> GetAuthenticationStateAsync() => new AuthenticationState(await GetAuthenticatedUserCallback()); + + public override Task<RemoteAuthenticationResult<RemoteAuthenticationState>> CompleteSignInAsync(RemoteAuthenticationContext<RemoteAuthenticationState> context) => CompleteSignInCallback(context); + + protected internal override ValueTask<ClaimsPrincipal> GetAuthenticatedUser() => GetAuthenticatedUserCallback(); + + public override Task<RemoteAuthenticationResult<RemoteAuthenticationState>> CompleteSignOutAsync(RemoteAuthenticationContext<RemoteAuthenticationState> context) => CompleteSignOutCallback(context); + + public override Task<RemoteAuthenticationResult<RemoteAuthenticationState>> SignInAsync(RemoteAuthenticationContext<RemoteAuthenticationState> context) => SignInCallback(context); + + public override Task<RemoteAuthenticationResult<RemoteAuthenticationState>> SignOutAsync(RemoteAuthenticationContext<RemoteAuthenticationState> context) => SignOutCallback(context); + } + + private class TestRenderer : Renderer + { + public TestRenderer(IServiceProvider services) + : base(services, NullLoggerFactory.Instance) + { + } + + public int Attach(IComponent component) => AssignRootComponentId(component); + + private static readonly Dispatcher _dispatcher = Dispatcher.CreateDefault(); + + public override Dispatcher Dispatcher => _dispatcher; + + protected override void HandleException(Exception exception) + => ExceptionDispatchInfo.Capture(exception).Throw(); + + protected override Task UpdateDisplayAsync(in RenderBatch renderBatch) => + Task.CompletedTask; + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/test/WebAssemblyAuthenticationServiceCollectionExtensionsTests.cs b/src/Components/WebAssembly/WebAssembly.Authentication/test/WebAssemblyAuthenticationServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..d9996a124d7fe40852078d642580ab20184631e4 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly.Authentication/test/WebAssemblyAuthenticationServiceCollectionExtensionsTests.cs @@ -0,0 +1,425 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.AspNetCore.Components.WebAssembly.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication +{ + public class WebAssemblyAuthenticationServiceCollectionExtensionsTests + { + [Fact] + public void CanResolve_AccessTokenProvider() + { + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + builder.Services.AddApiAuthorization(); + var host = builder.Build(); + + host.Services.GetRequiredService<IAccessTokenProvider>(); + } + + [Fact] + public void CanResolve_IRemoteAuthenticationService() + { + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + builder.Services.AddApiAuthorization(); + var host = builder.Build(); + + host.Services.GetRequiredService<IRemoteAuthenticationService<RemoteAuthenticationState>>(); + } + + [Fact] + public void ApiAuthorizationOptions_ConfigurationDefaultsGetApplied() + { + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + builder.Services.AddApiAuthorization(); + var host = builder.Build(); + + var options = host.Services.GetRequiredService<IOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>>(); + + var paths = options.Value.AuthenticationPaths; + + Assert.Equal("authentication/login", paths.LogInPath); + Assert.Equal("authentication/login-callback", paths.LogInCallbackPath); + Assert.Equal("authentication/login-failed", paths.LogInFailedPath); + Assert.Equal("authentication/register", paths.RegisterPath); + Assert.Equal("authentication/profile", paths.ProfilePath); + Assert.Equal("Identity/Account/Register", paths.RemoteRegisterPath); + Assert.Equal("Identity/Account/Manage", paths.RemoteProfilePath); + Assert.Equal("authentication/logout", paths.LogOutPath); + Assert.Equal("authentication/logout-callback", paths.LogOutCallbackPath); + Assert.Equal("authentication/logout-failed", paths.LogOutFailedPath); + Assert.Equal("authentication/logged-out", paths.LogOutSucceededPath); + + var user = options.Value.UserOptions; + Assert.Equal("Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests", user.AuthenticationType); + Assert.Equal("scope", user.ScopeClaim); + Assert.Equal("role", user.RoleClaim); + Assert.Equal("name", user.NameClaim); + + Assert.Equal( + "_configuration/Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests", + options.Value.ProviderOptions.ConfigurationEndpoint); + } + + [Fact] + public void ApiAuthorizationOptionsConfigurationCallback_GetsCalledOnce() + { + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + var calls = 0; + builder.Services.AddApiAuthorization(options => + { + calls++; + }); + + var host = builder.Build(); + + var options = host.Services.GetRequiredService<IOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>>(); + + var user = options.Value.UserOptions; + Assert.Equal("Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests", user.AuthenticationType); + + // Make sure that the defaults are applied on this overload + Assert.Equal("role", user.RoleClaim); + + Assert.Equal( + "_configuration/Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests", + options.Value.ProviderOptions.ConfigurationEndpoint); + + Assert.Equal(1, calls); + } + + [Fact] + public void ApiAuthorizationTestAuthenticationState_SetsUpConfiguration() + { + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + var calls = 0; + builder.Services.AddApiAuthorization<TestAuthenticationState>(options => calls++); + + var host = builder.Build(); + + var options = host.Services.GetRequiredService<IOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>>(); + + var user = options.Value.UserOptions; + // Make sure that the defaults are applied on this overload + Assert.Equal("role", user.RoleClaim); + + Assert.Equal( + "_configuration/Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests", + options.Value.ProviderOptions.ConfigurationEndpoint); + + var authenticationService = host.Services.GetService<IRemoteAuthenticationService<TestAuthenticationState>>(); + Assert.NotNull(authenticationService); + Assert.IsType<RemoteAuthenticationService<TestAuthenticationState, RemoteUserAccount, ApiAuthorizationProviderOptions>>(authenticationService); + + Assert.Equal(1, calls); + } + + [Fact] + public void ApiAuthorizationTestAuthenticationState_NoCallback_SetsUpConfiguration() + { + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + builder.Services.AddApiAuthorization<TestAuthenticationState>(); + + var host = builder.Build(); + + var options = host.Services.GetRequiredService<IOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>>(); + + var user = options.Value.UserOptions; + // Make sure that the defaults are applied on this overload + Assert.Equal("role", user.RoleClaim); + + Assert.Equal( + "_configuration/Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests", + options.Value.ProviderOptions.ConfigurationEndpoint); + + var authenticationService = host.Services.GetService<IRemoteAuthenticationService<TestAuthenticationState>>(); + Assert.NotNull(authenticationService); + Assert.IsType<RemoteAuthenticationService<TestAuthenticationState, RemoteUserAccount, ApiAuthorizationProviderOptions>>(authenticationService); + } + + [Fact] + public void ApiAuthorizationCustomAuthenticationStateAndAccount_SetsUpConfiguration() + { + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + var calls = 0; + builder.Services.AddApiAuthorization<TestAuthenticationState, TestAccount>(options => calls++); + + var host = builder.Build(); + + var options = host.Services.GetRequiredService<IOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>>(); + + var user = options.Value.UserOptions; + // Make sure that the defaults are applied on this overload + Assert.Equal("role", user.RoleClaim); + + Assert.Equal( + "_configuration/Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests", + options.Value.ProviderOptions.ConfigurationEndpoint); + + var authenticationService = host.Services.GetService<IRemoteAuthenticationService<TestAuthenticationState>>(); + Assert.NotNull(authenticationService); + Assert.IsType<RemoteAuthenticationService<TestAuthenticationState, TestAccount, ApiAuthorizationProviderOptions>>(authenticationService); + + Assert.Equal(1, calls); + } + + [Fact] + public void ApiAuthorizationTestAuthenticationStateAndAccount_NoCallback_SetsUpConfiguration() + { + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + builder.Services.AddApiAuthorization<TestAuthenticationState, TestAccount>(); + + var host = builder.Build(); + + var options = host.Services.GetRequiredService<IOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>>(); + + var user = options.Value.UserOptions; + // Make sure that the defaults are applied on this overload + Assert.Equal("role", user.RoleClaim); + + Assert.Equal( + "_configuration/Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests", + options.Value.ProviderOptions.ConfigurationEndpoint); + + var authenticationService = host.Services.GetService<IRemoteAuthenticationService<TestAuthenticationState>>(); + Assert.NotNull(authenticationService); + Assert.IsType<RemoteAuthenticationService<TestAuthenticationState, TestAccount, ApiAuthorizationProviderOptions>>(authenticationService); + } + + [Fact] + public void ApiAuthorizationOptions_DefaultsCanBeOverriden() + { + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + builder.Services.AddApiAuthorization(options => + { + options.AuthenticationPaths.LogInPath = "a"; + options.AuthenticationPaths.LogInCallbackPath = "b"; + options.AuthenticationPaths.LogInFailedPath = "c"; + options.AuthenticationPaths.RegisterPath = "d"; + options.AuthenticationPaths.ProfilePath = "e"; + options.AuthenticationPaths.RemoteRegisterPath = "f"; + options.AuthenticationPaths.RemoteProfilePath = "g"; + options.AuthenticationPaths.LogOutPath = "h"; + options.AuthenticationPaths.LogOutCallbackPath = "i"; + options.AuthenticationPaths.LogOutFailedPath = "j"; + options.AuthenticationPaths.LogOutSucceededPath = "k"; + options.UserOptions.AuthenticationType = "l"; + options.UserOptions.ScopeClaim = "m"; + options.UserOptions.RoleClaim = "n"; + options.UserOptions.NameClaim = "o"; + options.ProviderOptions.ConfigurationEndpoint = "p"; + }); + + var host = builder.Build(); + + var options = host.Services.GetRequiredService<IOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>>>(); + + var paths = options.Value.AuthenticationPaths; + + Assert.Equal("a", paths.LogInPath); + Assert.Equal("b", paths.LogInCallbackPath); + Assert.Equal("c", paths.LogInFailedPath); + Assert.Equal("d", paths.RegisterPath); + Assert.Equal("e", paths.ProfilePath); + Assert.Equal("f", paths.RemoteRegisterPath); + Assert.Equal("g", paths.RemoteProfilePath); + Assert.Equal("h", paths.LogOutPath); + Assert.Equal("i", paths.LogOutCallbackPath); + Assert.Equal("j", paths.LogOutFailedPath); + Assert.Equal("k", paths.LogOutSucceededPath); + + var user = options.Value.UserOptions; + Assert.Equal("l", user.AuthenticationType); + Assert.Equal("m", user.ScopeClaim); + Assert.Equal("n", user.RoleClaim); + Assert.Equal("o", user.NameClaim); + + Assert.Equal("p", options.Value.ProviderOptions.ConfigurationEndpoint); + } + + [Fact] + public void OidcOptions_ConfigurationDefaultsGetApplied() + { + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + builder.Services.Replace(ServiceDescriptor.Singleton<NavigationManager, TestNavigationManager>()); + builder.Services.AddOidcAuthentication(options => { }); + var host = builder.Build(); + + var options = host.Services.GetRequiredService<IOptions<RemoteAuthenticationOptions<OidcProviderOptions>>>(); + + var paths = options.Value.AuthenticationPaths; + + Assert.Equal("authentication/login", paths.LogInPath); + Assert.Equal("authentication/login-callback", paths.LogInCallbackPath); + Assert.Equal("authentication/login-failed", paths.LogInFailedPath); + Assert.Equal("authentication/register", paths.RegisterPath); + Assert.Equal("authentication/profile", paths.ProfilePath); + Assert.Null(paths.RemoteRegisterPath); + Assert.Null(paths.RemoteProfilePath); + Assert.Equal("authentication/logout", paths.LogOutPath); + Assert.Equal("authentication/logout-callback", paths.LogOutCallbackPath); + Assert.Equal("authentication/logout-failed", paths.LogOutFailedPath); + Assert.Equal("authentication/logged-out", paths.LogOutSucceededPath); + + var user = options.Value.UserOptions; + Assert.Null(user.AuthenticationType); + Assert.Null(user.ScopeClaim); + Assert.Null(user.RoleClaim); + Assert.Equal("name", user.NameClaim); + + var provider = options.Value.ProviderOptions; + Assert.Null(provider.Authority); + Assert.Null(provider.ClientId); + Assert.Equal(new[] { "openid", "profile" }, provider.DefaultScopes); + Assert.Equal("https://www.example.com/base/authentication/login-callback", provider.RedirectUri); + Assert.Equal("https://www.example.com/base/authentication/logout-callback", provider.PostLogoutRedirectUri); + } + + [Fact] + public void OidcOptions_DefaultsCanBeOverriden() + { + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + builder.Services.AddOidcAuthentication(options => + { + options.AuthenticationPaths.LogInPath = "a"; + options.AuthenticationPaths.LogInCallbackPath = "b"; + options.AuthenticationPaths.LogInFailedPath = "c"; + options.AuthenticationPaths.RegisterPath = "d"; + options.AuthenticationPaths.ProfilePath = "e"; + options.AuthenticationPaths.RemoteRegisterPath = "f"; + options.AuthenticationPaths.RemoteProfilePath = "g"; + options.AuthenticationPaths.LogOutPath = "h"; + options.AuthenticationPaths.LogOutCallbackPath = "i"; + options.AuthenticationPaths.LogOutFailedPath = "j"; + options.AuthenticationPaths.LogOutSucceededPath = "k"; + options.UserOptions.AuthenticationType = "l"; + options.UserOptions.ScopeClaim = "m"; + options.UserOptions.RoleClaim = "n"; + options.UserOptions.NameClaim = "o"; + options.ProviderOptions.Authority = "p"; + options.ProviderOptions.ClientId = "q"; + options.ProviderOptions.DefaultScopes.Clear(); + options.ProviderOptions.RedirectUri = "https://www.example.com/base/custom-login"; + options.ProviderOptions.PostLogoutRedirectUri = "https://www.example.com/base/custom-logout"; + }); + + var host = builder.Build(); + + var options = host.Services.GetRequiredService<IOptions<RemoteAuthenticationOptions<OidcProviderOptions>>>(); + + var paths = options.Value.AuthenticationPaths; + + Assert.Equal("a", paths.LogInPath); + Assert.Equal("b", paths.LogInCallbackPath); + Assert.Equal("c", paths.LogInFailedPath); + Assert.Equal("d", paths.RegisterPath); + Assert.Equal("e", paths.ProfilePath); + Assert.Equal("f", paths.RemoteRegisterPath); + Assert.Equal("g", paths.RemoteProfilePath); + Assert.Equal("h", paths.LogOutPath); + Assert.Equal("i", paths.LogOutCallbackPath); + Assert.Equal("j", paths.LogOutFailedPath); + Assert.Equal("k", paths.LogOutSucceededPath); + + var user = options.Value.UserOptions; + Assert.Equal("l", user.AuthenticationType); + Assert.Equal("m", user.ScopeClaim); + Assert.Equal("n", user.RoleClaim); + Assert.Equal("o", user.NameClaim); + + var provider = options.Value.ProviderOptions; + Assert.Equal("p", provider.Authority); + Assert.Equal("q", provider.ClientId); + Assert.Equal(Array.Empty<string>(), provider.DefaultScopes); + Assert.Equal("https://www.example.com/base/custom-login", provider.RedirectUri); + Assert.Equal("https://www.example.com/base/custom-logout", provider.PostLogoutRedirectUri); + } + + [Fact] + public void AddOidc_ConfigurationGetsCalledOnce() + { + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + var calls = 0; + + builder.Services.AddOidcAuthentication(options => calls++); + builder.Services.Replace(ServiceDescriptor.Singleton(typeof(NavigationManager), new TestNavigationManager())); + + var host = builder.Build(); + + var options = host.Services.GetRequiredService<IOptions<RemoteAuthenticationOptions<OidcProviderOptions>>>(); + Assert.Equal("name", options.Value.UserOptions.NameClaim); + + Assert.Equal(1, calls); + } + + [Fact] + public void AddOidc_CustomState_SetsUpConfiguration() + { + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + var calls = 0; + + builder.Services.AddOidcAuthentication<TestAuthenticationState>(options => options.ProviderOptions.Authority = (++calls).ToString()); + builder.Services.Replace(ServiceDescriptor.Singleton(typeof(NavigationManager), new TestNavigationManager())); + + var host = builder.Build(); + + var options = host.Services.GetRequiredService<IOptions<RemoteAuthenticationOptions<OidcProviderOptions>>>(); + // Make sure options are applied + Assert.Equal("name", options.Value.UserOptions.NameClaim); + + Assert.Equal("1", options.Value.ProviderOptions.Authority); + + var authenticationService = host.Services.GetService<IRemoteAuthenticationService<TestAuthenticationState>>(); + Assert.NotNull(authenticationService); + Assert.IsType<RemoteAuthenticationService<TestAuthenticationState, RemoteUserAccount, OidcProviderOptions>>(authenticationService); + } + + [Fact] + public void AddOidc_CustomStateAndAccount_SetsUpConfiguration() + { + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + var calls = 0; + + builder.Services.AddOidcAuthentication<TestAuthenticationState, TestAccount>(options => options.ProviderOptions.Authority = (++calls).ToString()); + builder.Services.Replace(ServiceDescriptor.Singleton(typeof(NavigationManager), new TestNavigationManager())); + + var host = builder.Build(); + + var options = host.Services.GetRequiredService<IOptions<RemoteAuthenticationOptions<OidcProviderOptions>>>(); + // Make sure options are applied + Assert.Equal("name", options.Value.UserOptions.NameClaim); + + Assert.Equal("1", options.Value.ProviderOptions.Authority); + + var authenticationService = host.Services.GetService<IRemoteAuthenticationService<TestAuthenticationState>>(); + Assert.NotNull(authenticationService); + Assert.IsType<RemoteAuthenticationService<TestAuthenticationState, TestAccount, OidcProviderOptions>>(authenticationService); + } + + private class TestNavigationManager : NavigationManager + { + public TestNavigationManager() + { + Initialize("https://www.example.com/base/", "https://www.example.com/base/counter"); + } + + protected override void NavigateToCore(string uri, bool forceLoad) => throw new System.NotImplementedException(); + } + + private class TestAuthenticationState : RemoteAuthenticationState + { + } + + private class TestAccount : RemoteUserAccount + { + } + } +} diff --git a/src/Components/Blazor/Blazor/src/Hosting/EntrypointInvoker.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/EntrypointInvoker.cs similarity index 82% rename from src/Components/Blazor/Blazor/src/Hosting/EntrypointInvoker.cs rename to src/Components/WebAssembly/WebAssembly/src/Hosting/EntrypointInvoker.cs index 40a5d07de4312bbe885f91cf67bfc1801b47d606..5fdea9e41bf316e3ad5856e54a645b5081723a9c 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/EntrypointInvoker.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/EntrypointInvoker.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; -namespace Microsoft.AspNetCore.Blazor.Hosting +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting { internal static class EntrypointInvoker { @@ -15,34 +15,27 @@ namespace Microsoft.AspNetCore.Blazor.Hosting // In the future we may want Blazor.start to return something that exposes the possibly-async // entrypoint result to the JS caller. There's no requirement to do that today, and if we // do change this it will be non-breaking. - public static void InvokeEntrypoint(string assemblyName, string[] args) + public static async void InvokeEntrypoint(string assemblyName, string[] args) { - object entrypointResult; try { var assembly = Assembly.Load(assemblyName); var entrypoint = FindUnderlyingEntrypoint(assembly); var @params = entrypoint.GetParameters().Length == 1 ? new object[] { args ?? Array.Empty<string>() } : new object[] { }; - entrypointResult = entrypoint.Invoke(null, @params); + + var result = entrypoint.Invoke(null, @params); + if (result is Task resultTask) + { + // In the default case, this Task is backed by the WebAssemblyHost.RunAsync that never completes. + // Awaiting it is allows catching any exception thrown by user code in MainAsync. + await resultTask; + } } catch (Exception syncException) { HandleStartupException(syncException); return; } - - // If the entrypoint is async, handle async exceptions in the same way that we would - // have handled sync ones - if (entrypointResult is Task entrypointTask) - { - entrypointTask.ContinueWith(task => - { - if (task.Exception != null) - { - HandleStartupException(task.Exception); - } - }); - } } private static MethodBase FindUnderlyingEntrypoint(Assembly assembly) diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/IWebAssemblyHostEnvironment.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/IWebAssemblyHostEnvironment.cs new file mode 100644 index 0000000000000000000000000000000000000000..f9c0c3cb13e1c37fce2260a45a92956ae7ca5f5e --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/IWebAssemblyHostEnvironment.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting +{ + /// <summary> + /// Provides information about the hosting environment an application is running in. + /// </summary> + public interface IWebAssemblyHostEnvironment + { + /// <summary> + /// Gets the name of the environment. This is configured to use the environment of the application hosting the Blazor WebAssembly application. + /// Configured to "Production" when not specified by the host. + /// </summary> + string Environment { get; } + + /// <summary> + /// Gets the base address for the application. This is typically derived from the <c>>base href<</c> value in the host page. + /// </summary> + string BaseAddress { get; } + } +} diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/LoggingBuilder.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/LoggingBuilder.cs new file mode 100644 index 0000000000000000000000000000000000000000..66c21904517291a4a06bc9adbdfb7f33e4e4079e --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/LoggingBuilder.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting +{ + internal class LoggingBuilder : ILoggingBuilder + { + public LoggingBuilder(IServiceCollection services) + { + Services = services; + } + + public IServiceCollection Services { get; } + } +} diff --git a/src/Components/Blazor/Blazor/src/Hosting/RootComponentMapping.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/RootComponentMapping.cs similarity index 96% rename from src/Components/Blazor/Blazor/src/Hosting/RootComponentMapping.cs rename to src/Components/WebAssembly/WebAssembly/src/Hosting/RootComponentMapping.cs index 53b34d882285df7bae74bb18649a73ab9ace5b39..22429279fdd0a697e652bb3688d102b1af71693c 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/RootComponentMapping.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/RootComponentMapping.cs @@ -4,7 +4,7 @@ using System; using Microsoft.AspNetCore.Components; -namespace Microsoft.AspNetCore.Blazor.Hosting +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting { /// <summary> /// Defines a mapping between a root <see cref="IComponent"/> and a DOM element selector. diff --git a/src/Components/Blazor/Blazor/src/Hosting/RootComponentMappingCollection.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/RootComponentMappingCollection.cs similarity index 97% rename from src/Components/Blazor/Blazor/src/Hosting/RootComponentMappingCollection.cs rename to src/Components/WebAssembly/WebAssembly/src/Hosting/RootComponentMappingCollection.cs index 0e488f0deb85f54c624ac029b6d396c945151900..ab4e1d82233ef721558655ff42ba89c8fbcb7513 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/RootComponentMappingCollection.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/RootComponentMappingCollection.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using Microsoft.AspNetCore.Components; -namespace Microsoft.AspNetCore.Blazor.Hosting +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting { /// <summary> /// Defines a collection of <see cref="RootComponentMapping"/> items. diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/SatelliteResourcesLoader.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/SatelliteResourcesLoader.cs new file mode 100644 index 0000000000000000000000000000000000000000..79f780c58496b4530e29c32a189d4908abc121c6 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/SatelliteResourcesLoader.cs @@ -0,0 +1,86 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebAssembly.Services; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting +{ + internal class SatelliteResourcesLoader + { + internal const string GetSatelliteAssemblies = "window.Blazor._internal.getSatelliteAssemblies"; + internal const string ReadSatelliteAssemblies = "window.Blazor._internal.readSatelliteAssemblies"; + + private readonly WebAssemblyJSRuntimeInvoker _invoker; + + // For unit testing. + internal SatelliteResourcesLoader(WebAssemblyJSRuntimeInvoker invoker) + { + _invoker = invoker; + } + + public virtual async ValueTask LoadCurrentCultureResourcesAsync() + { + var culturesToLoad = GetCultures(CultureInfo.CurrentCulture); + + if (culturesToLoad.Count == 0) + { + return; + } + + // Now that we know the cultures we care about, let WebAssemblyResourceLoader (in JavaScript) load these + // assemblies. We effectively want to resovle a Task<byte[][]> but there is no way to express this + // using interop. We'll instead do this in two parts: + // getSatelliteAssemblies resolves when all satellite assemblies to be loaded in .NET are fetched and available in memory. + var count = (int)await _invoker.InvokeUnmarshalled<string[], object, object, Task<object>>( + GetSatelliteAssemblies, + culturesToLoad.ToArray(), + null, + null); + + if (count == 0) + { + return; + } + + // readSatelliteAssemblies resolves the assembly bytes + var assemblies = _invoker.InvokeUnmarshalled<object, object, object, object[]>( + ReadSatelliteAssemblies, + null, + null, + null); + + for (var i = 0; i < assemblies.Length; i++) + { + Assembly.Load((byte[])assemblies[i]); + } + } + + internal static List<string> GetCultures(CultureInfo cultureInfo) + { + var culturesToLoad = new List<string>(); + + // Once WASM is ready, we have to use .NET's assembly loading to load additional assemblies. + // First calculate all possible cultures that the application might want to load. We do this by + // starting from the current culture and walking up the graph of parents. + // At the end of the the walk, we'll have a list of culture names that look like + // [ "fr-FR", "fr" ] + while (cultureInfo != null && cultureInfo != CultureInfo.InvariantCulture) + { + culturesToLoad.Add(cultureInfo.Name); + + if (cultureInfo.Parent == cultureInfo) + { + break; + } + + cultureInfo = cultureInfo.Parent; + } + + return culturesToLoad; + } + } +} diff --git a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHost.cs similarity index 84% rename from src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs rename to src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHost.cs index 02c2693ff7eceb0a0dd4f87c14b56f45a68af8ad..c81dd2ac28a934346bc94b262b42f032d1fa783a 100644 --- a/src/Components/Blazor/Blazor/src/Hosting/WebAssemblyHost.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHost.cs @@ -4,12 +4,14 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Blazor.Rendering; +using Microsoft.AspNetCore.Components.WebAssembly.Infrastructure; +using Microsoft.AspNetCore.Components.WebAssembly.Rendering; +using Microsoft.AspNetCore.Components.WebAssembly.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Blazor.Hosting +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting { /// <summary> /// A host object for Blazor running under WebAssembly. Use <see cref="WebAssemblyHostBuilder"/> @@ -40,7 +42,6 @@ namespace Microsoft.AspNetCore.Blazor.Hosting // To ensure JS-invoked methods don't get linked out, have a reference to their enclosing types GC.KeepAlive(typeof(EntrypointInvoker)); GC.KeepAlive(typeof(JSInteropMethods)); - GC.KeepAlive(typeof(WebAssemblyEventDispatcher)); _services = services; _scope = scope; @@ -58,6 +59,8 @@ namespace Microsoft.AspNetCore.Blazor.Hosting /// </summary> public IServiceProvider Services => _scope.ServiceProvider; + internal SatelliteResourcesLoader SatelliteResourcesLoader { get; set; } = new SatelliteResourcesLoader(WebAssemblyJSRuntimeInvoker.Instance); + /// <summary> /// Disposes the host asynchronously. /// </summary> @@ -93,7 +96,7 @@ namespace Microsoft.AspNetCore.Blazor.Hosting } /// <summary> - /// Runs the application associated with this host. + /// Runs the application associated with this host. /// </summary> /// <returns>A <see cref="Task"/> which represents exit of the application.</returns> /// <remarks> @@ -118,6 +121,12 @@ namespace Microsoft.AspNetCore.Blazor.Hosting _started = true; + // EntryPointInvoker loads satellite assemblies for the application default culture. + // Application developers might have configured the culture based on some ambient state + // such as local storage, url etc as part of their Program.Main(Async). + // This is the earliest opportunity to fetch satellite assemblies for this selection. + await SatelliteResourcesLoader.LoadCurrentCultureResourcesAsync(); + var tcs = new TaskCompletionSource<object>(); using (cancellationToken.Register(() => { tcs.TrySetResult(null); })) diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs new file mode 100644 index 0000000000000000000000000000000000000000..f81607bc90debe487e4bc3f6c56939ab2abea1a1 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs @@ -0,0 +1,199 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Components.WebAssembly.Services; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Json; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.JSInterop; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting +{ + /// <summary> + /// A builder for configuring and creating a <see cref="WebAssemblyHost"/>. + /// </summary> + public sealed class WebAssemblyHostBuilder + { + private Func<IServiceProvider> _createServiceProvider; + + /// <summary> + /// Creates an instance of <see cref="WebAssemblyHostBuilder"/> using the most common + /// conventions and settings. + /// </summary> + /// <param name="args">The argument passed to the application's main method.</param> + /// <returns>A <see cref="WebAssemblyHostBuilder"/>.</returns> + public static WebAssemblyHostBuilder CreateDefault(string[] args = default) + { + // We don't use the args for anything right now, but we want to accept them + // here so that it shows up this way in the project templates. + args ??= Array.Empty<string>(); + var builder = new WebAssemblyHostBuilder(WebAssemblyJSRuntimeInvoker.Instance); + + // Right now we don't have conventions or behaviors that are specific to this method + // however, making this the default for the template allows us to add things like that + // in the future, while giving `new WebAssemblyHostBuilder` as an opt-out of opinionated + // settings. + return builder; + } + + /// <summary> + /// Creates an instance of <see cref="WebAssemblyHostBuilder"/> with the minimal configuration. + /// </summary> + internal WebAssemblyHostBuilder(WebAssemblyJSRuntimeInvoker jsRuntimeInvoker) + { + // Private right now because we don't have much reason to expose it. This can be exposed + // in the future if we want to give people a choice between CreateDefault and something + // less opinionated. + Configuration = new WebAssemblyHostConfiguration(); + RootComponents = new RootComponentMappingCollection(); + Services = new ServiceCollection(); + Logging = new LoggingBuilder(Services); + + // Retrieve required attributes from JSRuntimeInvoker + InitializeNavigationManager(jsRuntimeInvoker); + InitializeDefaultServices(); + + var hostEnvironment = InitializeEnvironment(jsRuntimeInvoker); + HostEnvironment = hostEnvironment; + + _createServiceProvider = () => + { + return Services.BuildServiceProvider(validateScopes: WebAssemblyHostEnvironmentExtensions.IsDevelopment(hostEnvironment)); + }; + } + + private void InitializeNavigationManager(WebAssemblyJSRuntimeInvoker jsRuntimeInvoker) + { + var baseUri = jsRuntimeInvoker.InvokeUnmarshalled<object, object, object, string>(BrowserNavigationManagerInterop.GetBaseUri, null, null, null); + var uri = jsRuntimeInvoker.InvokeUnmarshalled<object, object, object, string>(BrowserNavigationManagerInterop.GetLocationHref, null, null, null); + + WebAssemblyNavigationManager.Instance = new WebAssemblyNavigationManager(baseUri, uri); + } + + private WebAssemblyHostEnvironment InitializeEnvironment(WebAssemblyJSRuntimeInvoker jsRuntimeInvoker) + { + var applicationEnvironment = jsRuntimeInvoker.InvokeUnmarshalled<object, object, object, string>( + "Blazor._internal.getApplicationEnvironment", null, null, null); + var hostEnvironment = new WebAssemblyHostEnvironment(applicationEnvironment, WebAssemblyNavigationManager.Instance.BaseUri); + + Services.AddSingleton<IWebAssemblyHostEnvironment>(hostEnvironment); + + var configFiles = new[] + { + "appsettings.json", + $"appsettings.{applicationEnvironment}.json" + }; + + foreach (var configFile in configFiles) + { + var appSettingsJson = jsRuntimeInvoker.InvokeUnmarshalled<string, object, object, byte[]>( + "Blazor._internal.getConfig", configFile, null, null); + + if (appSettingsJson != null) + { + // Perf: Using this over AddJsonStream. This allows the linker to trim out the "File"-specific APIs and assemblies + // for Configuration, of where there are several. + Configuration.Add<JsonStreamConfigurationSource>(s => s.Stream = new MemoryStream(appSettingsJson)); + } + } + + return hostEnvironment; + } + + /// <summary> + /// Gets an <see cref="WebAssemblyHostConfiguration"/> that can be used to customize the application's + /// configuration sources and read configuration attributes. + /// </summary> + public WebAssemblyHostConfiguration Configuration { get; } + + /// <summary> + /// Gets the collection of root component mappings configured for the application. + /// </summary> + public RootComponentMappingCollection RootComponents { get; } + + /// <summary> + /// Gets the service collection. + /// </summary> + public IServiceCollection Services { get; } + + /// <summary> + /// Gets information about the app's host environment. + /// </summary> + public IWebAssemblyHostEnvironment HostEnvironment { get; } + + /// <summary> + /// Gets the logging builder for configuring logging services. + /// </summary> + public ILoggingBuilder Logging { get; } + + /// <summary> + /// Registers a <see cref="IServiceProviderFactory{TBuilder}" /> instance to be used to create the <see cref="IServiceProvider" />. + /// </summary> + /// <param name="factory">The <see cref="IServiceProviderFactory{TBuilder}" />.</param> + /// <param name="configure"> + /// A delegate used to configure the <typeparamref T="TBuilder" />. This can be used to configure services using + /// APIS specific to the <see cref="IServiceProviderFactory{TBuilder}" /> implementation. + /// </param> + /// <typeparam name="TBuilder">The type of builder provided by the <see cref="IServiceProviderFactory{TBuilder}" />.</typeparam> + /// <remarks> + /// <para> + /// <see cref="ConfigureContainer{TBuilder}(IServiceProviderFactory{TBuilder}, Action{TBuilder})"/> is called by <see cref="Build"/> + /// and so the delegate provided by <paramref name="configure"/> will run after all other services have been registered. + /// </para> + /// <para> + /// Multiple calls to <see cref="ConfigureContainer{TBuilder}(IServiceProviderFactory{TBuilder}, Action{TBuilder})"/> will replace + /// the previously stored <paramref name="factory"/> and <paramref name="configure"/> delegate. + /// </para> + /// </remarks> + public void ConfigureContainer<TBuilder>(IServiceProviderFactory<TBuilder> factory, Action<TBuilder> configure = null) + { + if (factory == null) + { + throw new ArgumentNullException(nameof(factory)); + } + + _createServiceProvider = () => + { + var container = factory.CreateBuilder(Services); + configure?.Invoke(container); + return factory.CreateServiceProvider(container); + }; + } + + /// <summary> + /// Builds a <see cref="WebAssemblyHost"/> instance based on the configuration of this builder. + /// </summary> + /// <returns>A <see cref="WebAssemblyHost"/> object.</returns> + public WebAssemblyHost Build() + { + // Intentionally overwrite configuration with the one we're creating. + Services.AddSingleton<IConfiguration>(Configuration); + + // A Blazor application always runs in a scope. Since we want to make it possible for the user + // to configure services inside *that scope* inside their startup code, we create *both* the + // service provider and the scope here. + var services = _createServiceProvider(); + var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope(); + + return new WebAssemblyHost(services, scope, Configuration, RootComponents.ToArray()); + } + + internal void InitializeDefaultServices() + { + Services.AddSingleton<IJSRuntime>(DefaultWebAssemblyJSRuntime.Instance); + Services.AddSingleton<NavigationManager>(WebAssemblyNavigationManager.Instance); + Services.AddSingleton<INavigationInterception>(WebAssemblyNavigationInterception.Instance); + Services.AddLogging(builder => { + builder.AddProvider(new WebAssemblyConsoleLoggerProvider(DefaultWebAssemblyJSRuntime.Instance)); + }); + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostConfiguration.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostConfiguration.cs new file mode 100644 index 0000000000000000000000000000000000000000..9bbce6ed72ac2ab09d796c85b606fef92cebcf45 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostConfiguration.cs @@ -0,0 +1,188 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting +{ + /// <summary> + /// WebAssemblyHostConfiguration is a class that implements the interface of an IConfiguration, + /// IConfigurationRoot, and IConfigurationBuilder. It can be used to simulatneously build + /// and read from a configuration object. + /// </summary> + public class WebAssemblyHostConfiguration : IConfiguration, IConfigurationRoot, IConfigurationBuilder + { + private readonly List<IConfigurationProvider> _providers = new List<IConfigurationProvider>(); + private readonly List<IConfigurationSource> _sources = new List<IConfigurationSource>(); + + private readonly List<IDisposable> _changeTokenRegistrations = new List<IDisposable>(); + private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken(); + + /// <summary> + /// Gets the sources used to obtain configuration values. + /// </summary> + IList<IConfigurationSource> IConfigurationBuilder.Sources => new ReadOnlyCollection<IConfigurationSource>(_sources.ToList()); + + /// <summary> + /// Gets the providers used to obtain configuration values. + /// </summary> + IEnumerable<IConfigurationProvider> IConfigurationRoot.Providers => new ReadOnlyCollection<IConfigurationProvider>(_providers.ToList()); + + /// <summary> + /// Gets a key/value collection that can be used to share data between the <see cref="IConfigurationBuilder"/> + /// and the registered <see cref="IConfigurationProvider"/> instances. + /// </summary> + // In this implementation, this largely exists as a way to satisfy the + // requirements of the IConfigurationBuilder and is not populated by + // the WebAssemblyHostConfiguration with any meaningful info. + IDictionary<string, object> IConfigurationBuilder.Properties { get; } = new Dictionary<string, object>(); + + /// <inheritdoc /> + public string this[string key] + { + get + { + // Iterate through the providers in reverse to extract + // the value from the most recently inserted provider. + for (var i = _providers.Count - 1; i >= 0; i--) + { + var provider = _providers[i]; + + if (provider.TryGet(key, out var value)) + { + return value; + } + } + + return null; + } + set + { + if (_providers.Count == 0) + { + throw new InvalidOperationException("Can only set property if at least one provider has been inserted."); + } + + foreach (var provider in _providers) + { + provider.Set(key, value); + } + + } + } + + /// <summary> + /// Gets a configuration sub-section with the specified key. + /// </summary> + /// <param name="key">The key of the configuration section.</param> + /// <returns>The <see cref="IConfigurationSection"/>.</returns> + /// <remarks> + /// This method will never return <c>null</c>. If no matching sub-section is found with the specified key, + /// an empty <see cref="IConfigurationSection"/> will be returned. + /// </remarks> + public IConfigurationSection GetSection(string key) => new ConfigurationSection(this, key); + + /// <summary> + /// Gets the immediate descendant configuration sub-sections. + /// </summary> + /// <returns>The configuration sub-sections.</returns> + IEnumerable<IConfigurationSection> IConfiguration.GetChildren() + { + return _providers + .SelectMany(s => s.GetChildKeys(Enumerable.Empty<string>(), null)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(key => this.GetSection(key)) + .ToList(); + } + + /// <summary> + /// Returns a <see cref="IChangeToken"/> that can be used to observe when this configuration is reloaded. + /// </summary> + /// <returns>The <see cref="IChangeToken"/>.</returns> + public IChangeToken GetReloadToken() => _changeToken; + + /// <summary> + /// Force the configuration values to be reloaded from the underlying sources. + /// </summary> + public void Reload() + { + foreach (var provider in _providers) + { + provider.Load(); + } + RaiseChanged(); + } + + private void RaiseChanged() + { + var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken()); + previousToken.OnReload(); + } + + /// <summary> + /// Adds a new configuration source, retrieves the provider for the source, and + /// adds a change listener that triggers a reload of the provider whenever a change + /// is detected. + /// </summary> + /// <param name="source">The configuration source to add.</param> + /// <returns>The same <see cref="IConfigurationBuilder"/>.</returns> + public IConfigurationBuilder Add(IConfigurationSource source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + // Ads this source and its associated provider to the source + // and provider references in this class. We make sure to load + // the data from the provider so that values are properly initialized. + _sources.Add(source); + var provider = source.Build(this); + provider.Load(); + + // Add a handler that will detect when the the configuration + // provider has reloaded data. This will invoke the RaiseChanged + // method which maps changes in individual providers to the change + // token on the WebAssemblyHostConfiguration object. + _changeTokenRegistrations.Add(ChangeToken.OnChange(() => provider.GetReloadToken(), () => RaiseChanged())); + + // We keep a list of providers in this class so that we can map + // set and get methods on this class to the set and get methods + // on the individual configuration providers. + _providers.Add(provider); + return this; + } + + /// <summary> + /// Builds an <see cref="IConfiguration"/> with keys and values from the set of providers registered in + /// <see cref="IConfigurationRoot.Providers"/>. + /// </summary> + /// <returns>An <see cref="IConfigurationRoot"/> with keys and values from the registered providers.</returns> + public IConfigurationRoot Build() + { + return this; + } + + /// <inheritdoc /> + public void Dispose() + { + // dispose change token registrations + foreach (var registration in _changeTokenRegistrations) + { + registration.Dispose(); + } + + // dispose providers + foreach (var provider in _providers) + { + (provider as IDisposable)?.Dispose(); + } + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostEnvironment.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostEnvironment.cs new file mode 100644 index 0000000000000000000000000000000000000000..53904c9f87430b28fff169bcd8ad292496f7ca5f --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostEnvironment.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting +{ + internal sealed class WebAssemblyHostEnvironment : IWebAssemblyHostEnvironment + { + public WebAssemblyHostEnvironment(string environment, string baseAddress) + { + Environment = environment; + BaseAddress = baseAddress; + } + + public string Environment { get; } + + public string BaseAddress { get; } + } +} diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostEnvironmentExtensions.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostEnvironmentExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..2aaeb94a93d417679d262ad478904ab39e352d9b --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostEnvironmentExtensions.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting +{ + public static class WebAssemblyHostEnvironmentExtensions + { + /// <summary> + /// Checks if the current hosting environment name is <c>Development</c>. + /// </summary> + /// <param name="hostingEnvironment">An instance of <see cref="IWebAssemblyHostEnvironment"/>.</param> + /// <returns>True if the environment name is <c>Development</c>, otherwise false.</returns> + public static bool IsDevelopment(this IWebAssemblyHostEnvironment hostingEnvironment) + { + if (hostingEnvironment == null) + { + throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + return hostingEnvironment.IsEnvironment("Development"); + } + + /// <summary> + /// Checks if the current hosting environment name is <c>Staging</c>. + /// </summary> + /// <param name="hostingEnvironment">An instance of <see cref="IWebAssemblyHostEnvironment"/>.</param> + /// <returns>True if the environment name is <c>Staging</c>, otherwise false.</returns> + public static bool IsStaging(this IWebAssemblyHostEnvironment hostingEnvironment) + { + if (hostingEnvironment == null) + { + throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + return hostingEnvironment.IsEnvironment("Staging"); + } + + /// <summary> + /// Checks if the current hosting environment name is <c>Production</c>. + /// </summary> + /// <param name="hostingEnvironment">An instance of <see cref="IWebAssemblyHostEnvironment"/>.</param> + /// <returns>True if the environment name is <c>Production</c>, otherwise false.</returns> + public static bool IsProduction(this IWebAssemblyHostEnvironment hostingEnvironment) + { + if (hostingEnvironment == null) + { + throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + return hostingEnvironment.IsEnvironment("Production"); + } + + /// <summary> + /// Compares the current hosting environment name against the specified value. + /// </summary> + /// <param name="hostingEnvironment">An instance of <see cref="IWebAssemblyHostEnvironment"/>.</param> + /// <param name="environmentName">Environment name to validate against.</param> + /// <returns>True if the specified name is the same as the current environment, otherwise false.</returns> + public static bool IsEnvironment( + this IWebAssemblyHostEnvironment hostingEnvironment, + string environmentName) + { + if (hostingEnvironment == null) + { + throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + return string.Equals( + hostingEnvironment.Environment, + environmentName, + StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly/src/Http/BrowserRequestCache.cs b/src/Components/WebAssembly/WebAssembly/src/Http/BrowserRequestCache.cs new file mode 100644 index 0000000000000000000000000000000000000000..2e43edc91d78f2564627db093add965566f4195d --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Http/BrowserRequestCache.cs @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Http +{ + /// <summary> + /// The cache mode of the request. It controls how the request will interact with the browser's HTTP cache. + /// See https://developer.mozilla.org/en-US/docs/Web/API/Request/cache + /// </summary> + public enum BrowserRequestCache + { + /// <summary> + /// The browser looks for a matching request in its HTTP cache. + /// </summary> + Default, + + /// <summary> + /// The browser fetches the resource from the remote server without first looking in the cache, + /// and will not update the cache with the downloaded resource. + /// </summary> + NoStore, + + /// <summary> + /// The browser fetches the resource from the remote server without first looking in the cache, + /// but then will update the cache with the downloaded resource. + /// </summary> + Reload, + + /// <summary> + /// The browser looks for a matching request in its HTTP cache. + /// <para> + /// If there is a match, fresh or stale, the browser will make a conditional request to the remote server. + /// If the server indicates that the resource has not changed, it will be returned from the cache. + /// Otherwise the resource will be downloaded from the server and the cache will be updated. + /// </para> + /// <para> + /// If there is no match, the browser will make a normal request, and will update the cache with the downloaded resource. + /// </para> + /// </summary> + NoCache, + + /// <summary> + /// The browser looks for a matching request in its HTTP cache. + /// <para> + /// If there is a match, fresh or stale, it will be returned from the cache. + /// </para> + /// <para> + /// If there is no match, the browser will make a normal request, and will update the cache with the downloaded resource. + /// </para> + /// </summary> + ForceCache, + + /// <summary> + /// The browser looks for a matching request in its HTTP cache. + /// Mode can only be used if the request's mode is "same-origin" + /// <para> + /// If there is a match, fresh or stale, it will be returned from the cache. + /// </para> + /// <para> + /// If there is no match, the browser will respond with a 504 Gateway timeout status. + /// </para> + /// </summary> + OnlyIfCached, + } +} diff --git a/src/Components/Blazor/Blazor/src/Http/FetchCredentialsOption.cs b/src/Components/WebAssembly/WebAssembly/src/Http/BrowserRequestCredentials.cs similarity index 89% rename from src/Components/Blazor/Blazor/src/Http/FetchCredentialsOption.cs rename to src/Components/WebAssembly/WebAssembly/src/Http/BrowserRequestCredentials.cs index 37afb23f697de275929a4ea6c0d531037028c90c..491454566543989d066fd118b49a7b0cb7359f32 100644 --- a/src/Components/Blazor/Blazor/src/Http/FetchCredentialsOption.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Http/BrowserRequestCredentials.cs @@ -1,12 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace Microsoft.AspNetCore.Blazor.Http +namespace Microsoft.AspNetCore.Components.WebAssembly.Http { /// <summary> /// Specifies a value for the 'credentials' option on outbound HTTP requests. /// </summary> - public enum FetchCredentialsOption + public enum BrowserRequestCredentials { /// <summary> /// Advises the browser never to send credentials (such as cookies or HTTP auth headers). diff --git a/src/Components/WebAssembly/WebAssembly/src/Http/BrowserRequestMode.cs b/src/Components/WebAssembly/WebAssembly/src/Http/BrowserRequestMode.cs new file mode 100644 index 0000000000000000000000000000000000000000..1155efef0bc189a0dccab5a5ab5dc3c969de9734 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Http/BrowserRequestMode.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.WebAssembly.Http +{ + /// <summary> + /// The mode of the request. This is used to determine if cross-origin requests lead to valid responses + /// </summary> + public enum BrowserRequestMode + { + /// <summary> + /// If a request is made to another origin with this mode set, the result is simply an error + /// </summary> + SameOrigin, + + /// <summary> + /// Prevents the method from being anything other than HEAD, GET or POST, and the headers from + /// being anything other than simple headers. + /// </summary> + NoCors, + + /// <summary> + /// Allows cross-origin requests, for example to access various APIs offered by 3rd party vendors. + /// </summary> + Cors, + + /// <summary> + /// A mode for supporting navigation. + /// </summary> + Navigate, + } +} diff --git a/src/Components/WebAssembly/WebAssembly/src/Http/WebAssemblyHttpRequestMessageExtensions.cs b/src/Components/WebAssembly/WebAssembly/src/Http/WebAssemblyHttpRequestMessageExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..6abac268cc5b25016be1634aa169c09dc54876e6 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Http/WebAssemblyHttpRequestMessageExtensions.cs @@ -0,0 +1,169 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Http +{ + /// <summary> + /// Extension methods for configuring an instance of <see cref="HttpRequestMessage"/> with browser-specific options. + /// </summary> + public static class WebAssemblyHttpRequestMessageExtensions + { + /// <summary> + /// Configures a value for the 'credentials' option for the HTTP request. + /// </summary> + /// <param name="requestMessage">The <see cref="HttpRequestMessage"/>.</param> + /// <param name="requestCredentials">The <see cref="BrowserRequestCredentials"/> option.</param> + /// <returns>The <see cref="HttpRequestMessage"/>.</returns> + /// <remarks> + /// See https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials + /// </remarks> + public static HttpRequestMessage SetBrowserRequestCredentials(this HttpRequestMessage requestMessage, BrowserRequestCredentials requestCredentials) + { + if (requestMessage is null) + { + throw new ArgumentNullException(nameof(requestMessage)); + } + + var stringOption = requestCredentials switch + { + BrowserRequestCredentials.Omit => "omit", + BrowserRequestCredentials.SameOrigin => "same-origin", + BrowserRequestCredentials.Include => "include", + _ => throw new InvalidOperationException($"Unsupported enum value {requestCredentials}.") + }; + + return SetBrowserRequestOption(requestMessage, "credentials", stringOption); + } + + /// <summary> + /// Configures a value for the 'cache' option for the HTTP request. + /// </summary> + /// <param name="requestMessage">The <see cref="HttpRequestMessage"/>.</param> + /// <param name="requestCache">The <see cref="BrowserRequestCache"/> option.</param> + /// <returns>The <see cref="HttpRequestMessage"/>.</returns>\ + /// <remarks> + /// See https://developer.mozilla.org/en-US/docs/Web/API/Request/cache + /// </remarks> + public static HttpRequestMessage SetBrowserRequestCache(this HttpRequestMessage requestMessage, BrowserRequestCache requestCache) + { + if (requestMessage is null) + { + throw new ArgumentNullException(nameof(requestMessage)); + } + + var stringOption = requestCache switch + { + BrowserRequestCache.Default => "default", + BrowserRequestCache.NoStore => "no-store", + BrowserRequestCache.Reload => "reload", + BrowserRequestCache.NoCache => "no-cache", + BrowserRequestCache.ForceCache => "force-cache", + BrowserRequestCache.OnlyIfCached => "only-if-cached", + _ => throw new InvalidOperationException($"Unsupported enum value {requestCache}.") + }; + + return SetBrowserRequestOption(requestMessage, "cache", stringOption); + } + + /// <summary> + /// Configures a value for the 'mode' option for the HTTP request. + /// </summary> + /// <param name="requestMessage">The <see cref="HttpRequestMessage"/>.</param> + /// <param name="requestMode">The <see cref="BrowserRequestMode"/>.</param> + /// <returns>The <see cref="HttpRequestMessage"/>.</returns>\ + /// <remarks> + /// See https://developer.mozilla.org/en-US/docs/Web/API/Request/mode + /// </remarks> + public static HttpRequestMessage SetBrowserRequestMode(this HttpRequestMessage requestMessage, BrowserRequestMode requestMode) + { + if (requestMessage is null) + { + throw new ArgumentNullException(nameof(requestMessage)); + } + + var stringOption = requestMode switch + { + BrowserRequestMode.SameOrigin => "same-origin", + BrowserRequestMode.NoCors => "no-cors", + BrowserRequestMode.Cors => "cors", + BrowserRequestMode.Navigate => "navigate", + _ => throw new InvalidOperationException($"Unsupported enum value {requestMode}.") + }; + + return SetBrowserRequestOption(requestMessage, "mode", stringOption); + } + + /// <summary> + /// Configures a value for the 'integrity' option for the HTTP request. + /// </summary> + /// <param name="requestMessage">The <see cref="HttpRequestMessage"/>.</param> + /// <param name="integrity">The subresource integrity descriptor.</param> + /// <returns>The <see cref="HttpRequestMessage"/>.</returns> + /// <remarks> + /// See https://developer.mozilla.org/en-US/docs/Web/API/Request/integrity + /// </remarks> + public static HttpRequestMessage SetBrowserRequestIntegrity(this HttpRequestMessage requestMessage, string integrity) + => SetBrowserRequestOption(requestMessage, "integrity", integrity); + + /// <summary> + /// Configures a value for the HTTP request. + /// </summary> + /// <param name="requestMessage">The <see cref="HttpRequestMessage"/>.</param> + /// <param name="name">The name of the option, which should correspond to a key defined on https://fetch.spec.whatwg.org/#requestinit</param> + /// <param name="value">The value, which must be JSON-serializable.</param> + /// <returns>The <see cref="HttpRequestMessage"/>.</returns> + /// <remarks> + /// See https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch + /// </remarks> + public static HttpRequestMessage SetBrowserRequestOption(this HttpRequestMessage requestMessage, string name, object value) + { + if (requestMessage is null) + { + throw new ArgumentNullException(nameof(requestMessage)); + } + + const string FetchRequestOptionsKey = "WebAssemblyFetchOptions"; + IDictionary<string, object> fetchOptions; + + if (requestMessage.Properties.TryGetValue(FetchRequestOptionsKey, out var entry)) + { + fetchOptions = (IDictionary<string, object>)entry; + } + else + { + fetchOptions = new Dictionary<string, object>(StringComparer.Ordinal); + requestMessage.Properties[FetchRequestOptionsKey] = fetchOptions; + } + + fetchOptions[name] = value; + + return requestMessage; + } + + /// <summary> + /// Configures streaming response for the HTTP request. + /// </summary> + /// <param name="requestMessage">The <see cref="HttpRequestMessage"/>.</param> + /// <param name="streamingEnabled"><see langword="true"/> if streaming is enabled; otherwise false.</param> + /// <returns>The <see cref="HttpRequestMessage"/>.</returns> + /// <remarks> + /// This API is only effective when the browser HTTP Fetch supports streaming. + /// See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream. + /// </remarks> + public static HttpRequestMessage SetBrowserResponseStreamingEnabled(this HttpRequestMessage requestMessage, bool streamingEnabled) + { + if (requestMessage is null) + { + throw new ArgumentNullException(nameof(requestMessage)); + } + + requestMessage.Properties["WebAssemblyEnableStreamingResponse"] = streamingEnabled; + + return requestMessage; + } + } +} diff --git a/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyEventDispatcher.cs b/src/Components/WebAssembly/WebAssembly/src/Infrastructure/JSInteropMethods.cs similarity index 55% rename from src/Components/Blazor/Blazor/src/Rendering/WebAssemblyEventDispatcher.cs rename to src/Components/WebAssembly/WebAssembly/src/Infrastructure/JSInteropMethods.cs index 77aa59cbc15fa7fea2b5003c73b9e4cbc2e3b55f..e31f92e8707a469fe6b2f4f19d08d32b9602219d 100644 --- a/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyEventDispatcher.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Infrastructure/JSInteropMethods.cs @@ -1,19 +1,32 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.ComponentModel; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Rendering; +using Microsoft.AspNetCore.Components.WebAssembly.Services; using Microsoft.JSInterop; -namespace Microsoft.AspNetCore.Blazor.Rendering +namespace Microsoft.AspNetCore.Components.WebAssembly.Infrastructure { /// <summary> - /// Dispatches events from JavaScript to a Blazor WebAssembly renderer. - /// Intended for internal use only. + /// Contains methods called by interop. Intended for framework use only, not supported for use in application + /// code. /// </summary> - public static class WebAssemblyEventDispatcher + [EditorBrowsable(EditorBrowsableState.Never)] + public static class JSInteropMethods { + /// <summary> + /// For framework use only. + /// </summary> + [JSInvokable(nameof(NotifyLocationChanged))] + public static void NotifyLocationChanged(string uri, bool isInterceptedLink) + { + WebAssemblyNavigationManager.Instance.SetLocation(uri, isInterceptedLink); + } + /// <summary> /// For framework use only. /// </summary> diff --git a/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj b/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj new file mode 100644 index 0000000000000000000000000000000000000000..f0a480a8b74dbaaf9f0e3bc543d9646200364efa --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.csproj @@ -0,0 +1,34 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + <Description>Build client-side single-page applications (SPAs) with Blazor running under WebAssembly.</Description> + <IsShippingPackage>true</IsShippingPackage> + <NoWarn>$(NoWarn);BL0006</NoWarn> + <GenerateDocumentationFile>true</GenerateDocumentationFile> + </PropertyGroup> + + <ItemGroup> + <Reference Include="Microsoft.AspNetCore.Components.Web" /> + <Reference Include="Microsoft.Extensions.Configuration.Json" /> + <Reference Include="Microsoft.Extensions.Logging" /> + <Reference Include="Microsoft.JSInterop.WebAssembly" /> + <ProjectReference + Include="..\..\WebAssemblyHttpHandler\src\Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj" + IncludeAssets="compile" /> + </ItemGroup> + + <ItemGroup> + <Compile Include="$(ComponentsSharedSourceRoot)src\BrowserNavigationManagerInterop.cs" /> + <Compile Include="$(ComponentsSharedSourceRoot)src\JsonSerializerOptionsProvider.cs" /> + <Compile Include="$(ComponentsSharedSourceRoot)src\WebEventData.cs" /> + <Compile Include="$(ComponentsSharedSourceRoot)src\ElementReferenceJsonConverter.cs" /> + </ItemGroup> + + <ItemGroup> + <InternalsVisibleTo Include="Microsoft.AspNetCore.Components.WebAssembly.Tests" /> + <InternalsVisibleTo Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests" /> + <InternalsVisibleTo Include="BasicTestApp" /> + </ItemGroup> + +</Project> diff --git a/src/Components/WebAssembly/WebAssembly/src/Properties/AssemblyInfo.cs b/src/Components/WebAssembly/WebAssembly/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..47bfb5013ffcd6ec4dc7b47c89af7bfb49e762fe --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/src/Components/Blazor/Blazor/src/Rendering/NullDispatcher.cs b/src/Components/WebAssembly/WebAssembly/src/Rendering/NullDispatcher.cs similarity index 96% rename from src/Components/Blazor/Blazor/src/Rendering/NullDispatcher.cs rename to src/Components/WebAssembly/WebAssembly/src/Rendering/NullDispatcher.cs index d39897b0a7c4e627663baf48f94c88b07e1fe2c6..2301ce09fbbaff5c03284dcaebd4b52405acc8a3 100644 --- a/src/Components/Blazor/Blazor/src/Rendering/NullDispatcher.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Rendering/NullDispatcher.cs @@ -5,7 +5,7 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; -namespace Microsoft.AspNetCore.Blazor.Rendering +namespace Microsoft.AspNetCore.Components.WebAssembly.Rendering { internal class NullDispatcher : Dispatcher { diff --git a/src/Components/Blazor/Blazor/src/Rendering/RendererRegistry.cs b/src/Components/WebAssembly/WebAssembly/src/Rendering/RendererRegistry.cs similarity index 96% rename from src/Components/Blazor/Blazor/src/Rendering/RendererRegistry.cs rename to src/Components/WebAssembly/WebAssembly/src/Rendering/RendererRegistry.cs index c0f1f98e2995d65f03d79ed20cac9a2a3e34e7fd..710ef8539621f6f42ad6f98379b6b94103ba4267 100644 --- a/src/Components/Blazor/Blazor/src/Rendering/RendererRegistry.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Rendering/RendererRegistry.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Microsoft.AspNetCore.Blazor.Rendering +namespace Microsoft.AspNetCore.Components.WebAssembly.Rendering { internal static class RendererRegistry { diff --git a/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs b/src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs similarity index 84% rename from src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs rename to src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs index 8651e14930c81e8b462d1fe323727ebd1e58e77c..d9e81acf15dd218d2ffce813b4b8d37769eb9211 100644 --- a/src/Components/Blazor/Blazor/src/Rendering/WebAssemblyRenderer.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Rendering/WebAssemblyRenderer.cs @@ -4,12 +4,12 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.AspNetCore.Blazor.Services; -using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.RenderTree; +using Microsoft.AspNetCore.Components.WebAssembly.Services; using Microsoft.Extensions.Logging; +using Microsoft.JSInterop.WebAssembly; -namespace Microsoft.AspNetCore.Blazor.Rendering +namespace Microsoft.AspNetCore.Components.WebAssembly.Rendering { /// <summary> /// Provides mechanisms for rendering <see cref="IComponent"/> instances in a @@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering /// </summary> internal class WebAssemblyRenderer : Renderer { + private readonly ILogger _logger; private readonly int _webAssemblyRendererId; private bool isDispatchingEvent; @@ -32,6 +33,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering { // The WebAssembly renderer registers and unregisters itself with the static registry _webAssemblyRendererId = RendererRegistry.Add(this); + _logger = loggerFactory.CreateLogger<WebAssemblyRenderer>(); } public override Dispatcher Dispatcher => NullDispatcher.Instance; @@ -72,7 +74,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering // When implementing support for out-of-process runtimes, we'll need to call this // asynchronously and ensure we surface any exceptions correctly. - WebAssemblyJSRuntime.Instance.Invoke<object>( + DefaultWebAssemblyJSRuntime.Instance.Invoke<object>( "Blazor._internal.attachRootComponentToElement", domElementSelector, componentId, @@ -91,7 +93,7 @@ namespace Microsoft.AspNetCore.Blazor.Rendering /// <inheritdoc /> protected override Task UpdateDisplayAsync(in RenderBatch batch) { - WebAssemblyJSRuntime.Instance.InvokeUnmarshalled<int, RenderBatch, object>( + DefaultWebAssemblyJSRuntime.Instance.InvokeUnmarshalled<int, RenderBatch, object>( "Blazor._internal.renderBatch", _webAssemblyRendererId, batch); @@ -102,17 +104,16 @@ namespace Microsoft.AspNetCore.Blazor.Rendering /// <inheritdoc /> protected override void HandleException(Exception exception) { - Console.Error.WriteLine($"Unhandled exception rendering component:"); if (exception is AggregateException aggregateException) { foreach (var innerException in aggregateException.Flatten().InnerExceptions) { - Console.Error.WriteLine(innerException); + Log.UnhandledExceptionRenderingComponent(_logger, innerException); } } else { - Console.Error.WriteLine(exception); + Log.UnhandledExceptionRenderingComponent(_logger, exception); } } @@ -193,5 +194,31 @@ namespace Microsoft.AspNetCore.Blazor.Rendering TaskCompletionSource = new TaskCompletionSource<object>(); } } + + private static class Log + { + private static readonly Action<ILogger, string, Exception> _unhandledExceptionRenderingComponent; + + private static class EventIds + { + public static readonly EventId UnhandledExceptionRenderingComponent = new EventId(100, "ExceptionRenderingComponent"); + } + + static Log() + { + _unhandledExceptionRenderingComponent = LoggerMessage.Define<string>( + LogLevel.Critical, + EventIds.UnhandledExceptionRenderingComponent, + "Unhandled exception rendering component: {Message}"); + } + + public static void UnhandledExceptionRenderingComponent(ILogger logger, Exception exception) + { + _unhandledExceptionRenderingComponent( + logger, + exception.Message, + exception); + } + } } } diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs b/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs new file mode 100644 index 0000000000000000000000000000000000000000..3ee84bd41243a507ef11bb265e74c36ca02375bd --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Services/DefaultWebAssemblyJSRuntime.cs @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.JSInterop.Infrastructure; +using Microsoft.JSInterop.WebAssembly; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Services +{ + internal sealed class DefaultWebAssemblyJSRuntime : WebAssemblyJSRuntime + { + internal static readonly DefaultWebAssemblyJSRuntime Instance = new DefaultWebAssemblyJSRuntime(); + + private DefaultWebAssemblyJSRuntime() + { + JsonSerializerOptions.Converters.Add(new ElementReferenceJsonConverter()); + } + + #pragma warning disable IDE0051 // Remove unused private members. Invoked via Mono's JS interop mechanism (invoke_method) + private static string InvokeDotNet(string assemblyName, string methodIdentifier, string dotNetObjectId, string argsJson) + { + var callInfo = new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), callId: null); + return DotNetDispatcher.Invoke(Instance, callInfo, argsJson); + } + + // Invoked via Mono's JS interop mechanism (invoke_method) + private static void EndInvokeJS(string argsJson) + => DotNetDispatcher.EndInvokeJS(Instance, argsJson); + + // Invoked via Mono's JS interop mechanism (invoke_method) + private static void BeginInvokeDotNet(string callId, string assemblyNameOrDotNetObjectId, string methodIdentifier, string argsJson) + { + // Figure out whether 'assemblyNameOrDotNetObjectId' is the assembly name or the instance ID + // We only need one for any given call. This helps to work around the limitation that we can + // only pass a maximum of 4 args in a call from JS to Mono WebAssembly. + string assemblyName; + long dotNetObjectId; + if (char.IsDigit(assemblyNameOrDotNetObjectId[0])) + { + dotNetObjectId = long.Parse(assemblyNameOrDotNetObjectId); + assemblyName = null; + } + else + { + dotNetObjectId = default; + assemblyName = assemblyNameOrDotNetObjectId; + } + + var callInfo = new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId, callId); + DotNetDispatcher.BeginInvokeDotNet(Instance, callInfo, argsJson); + } + #pragma warning restore IDE0051 + } +} diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLogger.cs b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLogger.cs new file mode 100644 index 0000000000000000000000000000000000000000..d8fb6685796386718b40dba8829ad306875c6ac0 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLogger.cs @@ -0,0 +1,175 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Text; +using Microsoft.Extensions.Logging; +using Microsoft.JSInterop; +using Microsoft.JSInterop.WebAssembly; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Services +{ + internal class WebAssemblyConsoleLogger<T> : ILogger<T>, ILogger + { + private static readonly string _loglevelPadding = ": "; + private static readonly string _messagePadding; + private static readonly string _newLineWithMessagePadding; + private static readonly StringBuilder _logBuilder = new StringBuilder(); + + private readonly string _name; + private readonly WebAssemblyJSRuntime _jsRuntime; + + static WebAssemblyConsoleLogger() + { + var logLevelString = GetLogLevelString(LogLevel.Information); + _messagePadding = new string(' ', logLevelString.Length + _loglevelPadding.Length); + _newLineWithMessagePadding = Environment.NewLine + _messagePadding; + } + + public WebAssemblyConsoleLogger(IJSRuntime jsRuntime) + : this(string.Empty, (WebAssemblyJSRuntime)jsRuntime) // Cast for DI + { + } + + public WebAssemblyConsoleLogger(string name, WebAssemblyJSRuntime jsRuntime) + { + _name = name ?? throw new ArgumentNullException(nameof(name)); + _jsRuntime = jsRuntime ?? throw new ArgumentNullException(nameof(jsRuntime)); + } + + public IDisposable BeginScope<TState>(TState state) + { + return NoOpDisposable.Instance; + } + + public bool IsEnabled(LogLevel logLevel) + { + return logLevel != LogLevel.None; + } + + public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) + { + if (!IsEnabled(logLevel)) + { + return; + } + + if (formatter == null) + { + throw new ArgumentNullException(nameof(formatter)); + } + + var message = formatter(state, exception); + + if (!string.IsNullOrEmpty(message) || exception != null) + { + WriteMessage(logLevel, _name, eventId.Id, message, exception); + } + } + + private void WriteMessage(LogLevel logLevel, string logName, int eventId, string message, Exception exception) + { + lock (_logBuilder) + { + try + { + CreateDefaultLogMessage(_logBuilder, logLevel, logName, eventId, message, exception); + var formattedMessage = _logBuilder.ToString(); + + switch (logLevel) + { + case LogLevel.Trace: + case LogLevel.Debug: + // Although https://console.spec.whatwg.org/#loglevel-severity claims that + // "console.debug" and "console.log" are synonyms, that doesn't match the + // behavior of browsers in the real world. Chromium only displays "debug" + // messages if you enable "Verbose" in the filter dropdown (which is off + // by default). As such "console.debug" is the best choice for messages + // with a lower severity level than "Information". + _jsRuntime.InvokeVoid("console.debug", formattedMessage); + break; + case LogLevel.Information: + _jsRuntime.InvokeVoid("console.info", formattedMessage); + break; + case LogLevel.Warning: + _jsRuntime.InvokeVoid("console.warn", formattedMessage); + break; + case LogLevel.Error: + _jsRuntime.InvokeVoid("console.error", formattedMessage); + break; + case LogLevel.Critical: + _jsRuntime.InvokeUnmarshalled<string, object>("Blazor._internal.dotNetCriticalError", formattedMessage); + break; + default: // LogLevel.None or invalid enum values + Console.WriteLine(formattedMessage); + break; + } + } + finally + { + _logBuilder.Clear(); + } + } + } + + private void CreateDefaultLogMessage(StringBuilder logBuilder, LogLevel logLevel, string logName, int eventId, string message, Exception exception) + { + logBuilder.Append(GetLogLevelString(logLevel)); + logBuilder.Append(_loglevelPadding); + logBuilder.Append(logName); + logBuilder.Append("["); + logBuilder.Append(eventId); + logBuilder.Append("]"); + + if (!string.IsNullOrEmpty(message)) + { + // message + logBuilder.AppendLine(); + logBuilder.Append(_messagePadding); + + var len = logBuilder.Length; + logBuilder.Append(message); + logBuilder.Replace(Environment.NewLine, _newLineWithMessagePadding, len, message.Length); + } + + // Example: + // System.InvalidOperationException + // at Namespace.Class.Function() in File:line X + if (exception != null) + { + // exception message + logBuilder.AppendLine(); + logBuilder.Append(exception.ToString()); + } + } + + private static string GetLogLevelString(LogLevel logLevel) + { + switch (logLevel) + { + case LogLevel.Trace: + return "trce"; + case LogLevel.Debug: + return "dbug"; + case LogLevel.Information: + return "info"; + case LogLevel.Warning: + return "warn"; + case LogLevel.Error: + return "fail"; + case LogLevel.Critical: + return "crit"; + default: + throw new ArgumentOutOfRangeException(nameof(logLevel)); + } + } + + private class NoOpDisposable : IDisposable + { + public static NoOpDisposable Instance = new NoOpDisposable(); + + public void Dispose() { } + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLoggerProvider.cs b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLoggerProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..cc3079d91746b3c4ca372043ab7901235ca53f17 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyConsoleLoggerProvider.cs @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using Microsoft.Extensions.Logging; +using Microsoft.JSInterop; +using Microsoft.JSInterop.WebAssembly; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Services +{ + /// <summary> + /// A provider of <see cref="WebAssemblyConsoleLogger{T}"/> instances. + /// </summary> + internal class WebAssemblyConsoleLoggerProvider : ILoggerProvider + { + private readonly ConcurrentDictionary<string, WebAssemblyConsoleLogger<object>> _loggers; + private readonly WebAssemblyJSRuntime _jsRuntime; + + /// <summary> + /// Creates an instance of <see cref="WebAssemblyConsoleLoggerProvider"/>. + /// </summary> + public WebAssemblyConsoleLoggerProvider(WebAssemblyJSRuntime jsRuntime) + { + _loggers = new ConcurrentDictionary<string, WebAssemblyConsoleLogger<object>>(); + _jsRuntime = jsRuntime; + } + + /// <inheritdoc /> + public ILogger CreateLogger(string name) + { + return _loggers.GetOrAdd(name, loggerName => new WebAssemblyConsoleLogger<object>(name, _jsRuntime)); + } + + /// <inheritdoc /> + public void Dispose() + { + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyJSRuntimeInvoker.cs b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyJSRuntimeInvoker.cs new file mode 100644 index 0000000000000000000000000000000000000000..4592c2ce9c4c84c13c56545a0b7297409d5e3f26 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyJSRuntimeInvoker.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.JSInterop.WebAssembly; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Services +{ + /// <summary> + /// This class exists to enable unit testing for code that needs to call + /// <see cref="WebAssemblyJSRuntime.InvokeUnmarshalled{T0, T1, T2, TResult}(string, T0, T1, T2)"/>. + /// + /// We should only use this in non-perf-critical code paths (for example, during hosting startup, + /// where we only call this a fixed number of times, and not during rendering where it might be + /// called arbitrarily frequently due to application logic). In perf-critical code paths, use + /// <see cref="DefaultWebAssemblyJSRuntime.Instance"/> and call it directly. + /// + /// It might not ultimately make any difference but we won't know until we integrate AoT support. + /// When AoT is used, it's possible that virtual dispatch will force fallback on the interpreter. + /// </summary> + internal class WebAssemblyJSRuntimeInvoker + { + public static WebAssemblyJSRuntimeInvoker Instance = new WebAssemblyJSRuntimeInvoker(); + + public virtual TResult InvokeUnmarshalled<T0, T1, T2, TResult>(string identifier, T0 arg0, T1 arg1, T2 arg2) + => DefaultWebAssemblyJSRuntime.Instance.InvokeUnmarshalled<T0, T1, T2, TResult>(identifier, arg0, arg1, arg2); + } +} diff --git a/src/Components/Blazor/Blazor/src/Services/WebAssemblyNavigationInterception.cs b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationInterception.cs similarity index 80% rename from src/Components/Blazor/Blazor/src/Services/WebAssemblyNavigationInterception.cs rename to src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationInterception.cs index 74eacbc4f5b81d8b5e3cf7d5fc4959c97b5712d5..f87c8cf95dd4875b57766d19bf002d73e6c94613 100644 --- a/src/Components/Blazor/Blazor/src/Services/WebAssemblyNavigationInterception.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationInterception.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Routing; using Interop = Microsoft.AspNetCore.Components.Web.BrowserNavigationManagerInterop; -namespace Microsoft.AspNetCore.Blazor.Services +namespace Microsoft.AspNetCore.Components.WebAssembly.Services { internal sealed class WebAssemblyNavigationInterception : INavigationInterception { @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Blazor.Services public Task EnableNavigationInterceptionAsync() { - WebAssemblyJSRuntime.Instance.Invoke<object>(Interop.EnableNavigationInterception); + DefaultWebAssemblyJSRuntime.Instance.Invoke<object>(Interop.EnableNavigationInterception); return Task.CompletedTask; } } diff --git a/src/Components/Blazor/Blazor/src/Services/WebAssemblyNavigationManager.cs b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationManager.cs similarity index 53% rename from src/Components/Blazor/Blazor/src/Services/WebAssemblyNavigationManager.cs rename to src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationManager.cs index 9d52c8c9a2b703bc37f98dc3dc271ee0a1de00d0..3fda8eaee86184879bdd2d8c420bad4d492e1758 100644 --- a/src/Components/Blazor/Blazor/src/Services/WebAssemblyNavigationManager.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Services/WebAssemblyNavigationManager.cs @@ -5,7 +5,7 @@ using System; using Microsoft.AspNetCore.Components; using Interop = Microsoft.AspNetCore.Components.Web.BrowserNavigationManagerInterop; -namespace Microsoft.AspNetCore.Blazor.Services +namespace Microsoft.AspNetCore.Components.WebAssembly.Services { /// <summary> /// Default client-side implementation of <see cref="NavigationManager"/>. @@ -15,21 +15,10 @@ namespace Microsoft.AspNetCore.Blazor.Services /// <summary> /// Gets the instance of <see cref="WebAssemblyNavigationManager"/>. /// </summary> - public static readonly WebAssemblyNavigationManager Instance = new WebAssemblyNavigationManager(); + public static WebAssemblyNavigationManager Instance { get; set; } - // For simplicity we force public consumption of the BrowserNavigationManager through - // a singleton. Only a single instance can be updated by the browser through - // interop. We can construct instances for testing. - internal WebAssemblyNavigationManager() + public WebAssemblyNavigationManager(string baseUri, string uri) { - } - - protected override void EnsureInitialized() - { - // As described in the comment block above, BrowserNavigationManager is only for - // client-side (Mono) use, so it's OK to rely on synchronicity here. - var baseUri = WebAssemblyJSRuntime.Instance.Invoke<string>(Interop.GetBaseUri); - var uri = WebAssemblyJSRuntime.Instance.Invoke<string>(Interop.GetLocationHref); Initialize(baseUri, uri); } @@ -47,7 +36,7 @@ namespace Microsoft.AspNetCore.Blazor.Services throw new ArgumentNullException(nameof(uri)); } - WebAssemblyJSRuntime.Instance.Invoke<object>(Interop.NavigateTo, uri, forceLoad); + DefaultWebAssemblyJSRuntime.Instance.Invoke<object>(Interop.NavigateTo, uri, forceLoad); } } } diff --git a/src/Components/Blazor/Blazor/test/Hosting/EntrypointInvokerTest.cs b/src/Components/WebAssembly/WebAssembly/test/Hosting/EntrypointInvokerTest.cs similarity index 98% rename from src/Components/Blazor/Blazor/test/Hosting/EntrypointInvokerTest.cs rename to src/Components/WebAssembly/WebAssembly/test/Hosting/EntrypointInvokerTest.cs index 60a3d1638b8f71cb465ddba9e1e652874a069542..eafc042993d4a856781a4c1b7dc6b73b8faa6db7 100644 --- a/src/Components/Blazor/Blazor/test/Hosting/EntrypointInvokerTest.cs +++ b/src/Components/WebAssembly/WebAssembly/test/Hosting/EntrypointInvokerTest.cs @@ -10,7 +10,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Xunit; -namespace Microsoft.AspNetCore.Blazor.Hosting +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting { public class EntrypointInvokerTest { @@ -145,7 +145,7 @@ namespace SomeApp var assembly = AssemblyLoadContext.Default.LoadFromStream(ms); var didMainExecuteProp = assembly.GetType("SomeApp.Program").GetProperty("DidMainExecute"); - didMainExecute = () => (bool)didMainExecuteProp.GetValue(null); + didMainExecute = () => (bool)didMainExecuteProp.GetValue(null); return assembly; } diff --git a/src/Components/Blazor/Blazor/test/Hosting/RootComponentMappingTest.cs b/src/Components/WebAssembly/WebAssembly/test/Hosting/RootComponentMappingTest.cs similarity index 94% rename from src/Components/Blazor/Blazor/test/Hosting/RootComponentMappingTest.cs rename to src/Components/WebAssembly/WebAssembly/test/Hosting/RootComponentMappingTest.cs index 7249402880711ce771c6d63efd7a12918aced71e..560653845690a18a3d9a7354e383298c19000f9d 100644 --- a/src/Components/Blazor/Blazor/test/Hosting/RootComponentMappingTest.cs +++ b/src/Components/WebAssembly/WebAssembly/test/Hosting/RootComponentMappingTest.cs @@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Components.Routing; using Microsoft.AspNetCore.Testing; using Xunit; -namespace Microsoft.AspNetCore.Blazor.Hosting +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting { public class RootComponentMappingTest { diff --git a/src/Components/WebAssembly/WebAssembly/test/Hosting/SatelliteResourcesLoaderTest.cs b/src/Components/WebAssembly/WebAssembly/test/Hosting/SatelliteResourcesLoaderTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..c4bb704bb412ffd9b89e3eb913d084ad0be77848 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/test/Hosting/SatelliteResourcesLoaderTest.cs @@ -0,0 +1,74 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Globalization; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebAssembly.Services; +using Microsoft.AspNetCore.Testing; +using Moq; +using Xunit; +using static Microsoft.AspNetCore.Components.WebAssembly.Hosting.SatelliteResourcesLoader; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting +{ + public class SatelliteResourcesLoaderTest + { + [Theory] + [InlineData("fr-FR", new[] { "fr-FR", "fr" })] + [InlineData("tzm-Latn-DZ", new[] { "tzm-Latn-DZ", "tzm-Latn", "tzm" })] + public void GetCultures_ReturnsCultureClosure(string cultureName, string[] expected) + { + // Arrange + var culture = new CultureInfo(cultureName); + + // Act + var actual = SatelliteResourcesLoader.GetCultures(culture); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public async Task LoadCurrentCultureResourcesAsync_ReadsAssemblies() + { + // Arrange + using var cultureReplacer = new CultureReplacer("en-GB"); + var invoker = new Mock<WebAssemblyJSRuntimeInvoker>(); + invoker.Setup(i => i.InvokeUnmarshalled<string[], object, object, Task<object>>(GetSatelliteAssemblies, new[] { "en-GB", "en" }, null, null)) + .Returns(Task.FromResult<object>(1)) + .Verifiable(); + + invoker.Setup(i => i.InvokeUnmarshalled<object, object, object, object[]>(ReadSatelliteAssemblies, null, null, null)) + .Returns(new object[] { File.ReadAllBytes(GetType().Assembly.Location) }) + .Verifiable(); + + var loader = new SatelliteResourcesLoader(invoker.Object); + + // Act + await loader.LoadCurrentCultureResourcesAsync(); + + // Assert + invoker.Verify(); + } + + [Fact] + public async Task LoadCurrentCultureResourcesAsync_DoesNotReadAssembliesWhenThereAreNone() + { + // Arrange + using var cultureReplacer = new CultureReplacer("en-GB"); + var invoker = new Mock<WebAssemblyJSRuntimeInvoker>(); + invoker.Setup(i => i.InvokeUnmarshalled<string[], object, object, Task<object>>(GetSatelliteAssemblies, new[] { "en-GB", "en" }, null, null)) + .Returns(Task.FromResult<object>(0)) + .Verifiable(); + + var loader = new SatelliteResourcesLoader(invoker.Object); + + // Act + await loader.LoadCurrentCultureResourcesAsync(); + + // Assert + invoker.Verify(i => i.InvokeUnmarshalled<object, object, object, object[]>(ReadSatelliteAssemblies, null, null, null), Times.Never()); + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..55c436596afb29dd06d35b256bca04e0221158d2 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs @@ -0,0 +1,257 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyModel; +using Microsoft.Extensions.Logging; +using Microsoft.JSInterop; +using Microsoft.JSInterop.WebAssembly; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting +{ + public class WebAssemblyHostBuilderTest + { + [Fact] + public void Build_AllowsConfiguringConfiguration() + { + // Arrange + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + + builder.Configuration.AddInMemoryCollection(new[] + { + new KeyValuePair<string, string>("key", "value"), + }); + + // Act + var host = builder.Build(); + + // Assert + Assert.Equal("value", host.Configuration["key"]); + } + + [Fact] + public void Build_AllowsConfiguringServices() + { + // Arrange + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + + // This test also verifies that we create a scope. + builder.Services.AddScoped<StringBuilder>(); + + // Act + var host = builder.Build(); + + // Assert + Assert.NotNull(host.Services.GetRequiredService<StringBuilder>()); + } + + [Fact] + public void Build_AllowsConfiguringContainer() + { + // Arrange + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + + builder.Services.AddScoped<StringBuilder>(); + var factory = new MyFakeServiceProviderFactory(); + builder.ConfigureContainer(factory); + + // Act + var host = builder.Build(); + + // Assert + Assert.True(factory.CreateServiceProviderCalled); + Assert.NotNull(host.Services.GetRequiredService<StringBuilder>()); + } + + [Fact] + public void Build_AllowsConfiguringContainer_WithDelegate() + { + // Arrange + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + + builder.Services.AddScoped<StringBuilder>(); + + var factory = new MyFakeServiceProviderFactory(); + builder.ConfigureContainer(factory, builder => + { + builder.ServiceCollection.AddScoped<List<string>>(); + }); + + // Act + var host = builder.Build(); + + // Assert + Assert.True(factory.CreateServiceProviderCalled); + Assert.NotNull(host.Services.GetRequiredService<StringBuilder>()); + Assert.NotNull(host.Services.GetRequiredService<List<string>>()); + } + + [Fact] + public void Build_InDevelopment_ConfiguresWithServiceProviderWithScopeValidation() + { + // Arrange + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker(environment: "Development")); + + builder.Services.AddScoped<StringBuilder>(); + builder.Services.AddSingleton<TestServiceThatTakesStringBuilder>(); + + // Act + var host = builder.Build(); + + // Assert + Assert.NotNull(host.Services.GetRequiredService<StringBuilder>()); + Assert.Throws<InvalidOperationException>(() => host.Services.GetRequiredService<TestServiceThatTakesStringBuilder>()); + } + + [Fact] + public void Build_InProduction_ConfiguresWithServiceProviderWithScopeValidation() + { + // Arrange + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + + builder.Services.AddScoped<StringBuilder>(); + builder.Services.AddSingleton<TestServiceThatTakesStringBuilder>(); + + // Act + var host = builder.Build(); + + // Assert + Assert.NotNull(host.Services.GetRequiredService<StringBuilder>()); + Assert.NotNull(host.Services.GetRequiredService<TestServiceThatTakesStringBuilder>()); + } + + [Fact] + public void Builder_InDevelopment_SetsHostEnvironmentProperty() + { + // Arrange + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker(environment: "Development")); + + // Assert + Assert.NotNull(builder.HostEnvironment); + Assert.True(WebAssemblyHostEnvironmentExtensions.IsDevelopment(builder.HostEnvironment)); + } + + [Fact] + public void Builder_CreatesNavigationManager() + { + // Arrange + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker(environment: "Development")); + + // Act + var host = builder.Build(); + + // Assert + var navigationManager = host.Services.GetRequiredService<NavigationManager>(); + Assert.NotNull(navigationManager); + Assert.Equal("https://www.example.com/", navigationManager.BaseUri); + Assert.Equal("https://www.example.com/awesome-part-that-will-be-truncated-in-tests/cool", navigationManager.Uri); + } + + private class TestServiceThatTakesStringBuilder + { + public TestServiceThatTakesStringBuilder(StringBuilder builder) { } + } + + + private class MyFakeDIBuilderThing + { + public MyFakeDIBuilderThing(IServiceCollection serviceCollection) + { + ServiceCollection = serviceCollection; + } + + public IServiceCollection ServiceCollection { get; } + } + + private class MyFakeServiceProviderFactory : IServiceProviderFactory<MyFakeDIBuilderThing> + { + public bool CreateServiceProviderCalled { get; set; } + + public MyFakeDIBuilderThing CreateBuilder(IServiceCollection services) + { + return new MyFakeDIBuilderThing(services); + } + + public IServiceProvider CreateServiceProvider(MyFakeDIBuilderThing containerBuilder) + { + // This is the best way to test the factory was actually used. The Host doesn't + // expose the *root* service provider, only a scoped instance. So we can return + // a different type here, but we have no way to inspect it. + CreateServiceProviderCalled = true; + return containerBuilder.ServiceCollection.BuildServiceProvider(); + } + } + + [Fact] + public void Build_AddsConfigurationToServices() + { + // Arrange + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + + builder.Configuration.AddInMemoryCollection(new[] + { + new KeyValuePair<string, string>("key", "value"), + }); + + // Act + var host = builder.Build(); + + // Assert + var configuration = host.Services.GetRequiredService<IConfiguration>(); + Assert.Equal("value", configuration["key"]); + } + + private static IReadOnlyList<Type> DefaultServiceTypes + { + get + { + return new Type[] + { + typeof(IJSRuntime), + typeof(NavigationManager), + typeof(INavigationInterception), + typeof(ILoggerFactory), + typeof(ILogger<>), + typeof(IWebAssemblyHostEnvironment), + }; + } + } + + [Fact] + public void Constructor_AddsDefaultServices() + { + // Arrange & Act + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + + foreach (var type in DefaultServiceTypes) + { + Assert.Single(builder.Services, d => d.ServiceType == type); + } + } + + [Fact] + public void Builder_SupportsConfiguringLogging() + { + // Arrange + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); + var provider = new Mock<ILoggerProvider>(); + + // Act + builder.Logging.AddProvider(provider.Object); + var host = builder.Build(); + + // Assert + var loggerProvider = host.Services.GetRequiredService<ILoggerProvider>(); + Assert.NotNull(loggerProvider); + Assert.Equal<ILoggerProvider>(provider.Object, loggerProvider); + + } + } +} diff --git a/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostConfigurationTest.cs b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostConfigurationTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..922293fa0a3a48a038e2691beda42fb3f7cd776a --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostConfigurationTest.cs @@ -0,0 +1,230 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Xunit; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Memory; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting +{ + public class WebAssemblyHostConfigurationTest + { + [Fact] + public void CanSetAndGetConfigurationValue() + { + // Arrange + var initialData = new Dictionary<string, string>() { + { "color", "blue" }, + { "type", "car" }, + { "wheels:year", "2008" }, + { "wheels:count", "4" }, + { "wheels:brand", "michelin" }, + { "wheels:brand:type", "rally" }, + }; + var memoryConfig = new MemoryConfigurationSource { InitialData = initialData }; + var configuration = new WebAssemblyHostConfiguration(); + + // Act + configuration.Add(memoryConfig); + configuration["type"] = "car"; + configuration["wheels:count"] = "6"; + + // Assert + Assert.Equal("car", configuration["type"]); + Assert.Equal("blue", configuration["color"]); + Assert.Equal("6", configuration["wheels:count"]); + } + + [Fact] + public void SettingValueUpdatesAllProviders() + { + // Arrange + var initialData = new Dictionary<string, string>() { { "color", "blue" } }; + var source1 = new MemoryConfigurationSource { InitialData = initialData }; + var source2 = new CustomizedTestConfigurationSource(); + var configuration = new WebAssemblyHostConfiguration(); + + // Act + configuration.Add(source1); + configuration.Add(source2); + configuration["type"] = "car"; + + // Assert + Assert.Equal("car", configuration["type"]); + IConfigurationRoot root = configuration; + Assert.All(root.Providers, provider => + { + provider.TryGet("type", out var value); + Assert.Equal("car", value); + }); + } + + [Fact] + public void CanGetChildren() + { + // Arrange + var initialData = new Dictionary<string, string>() { { "color", "blue" } }; + var memoryConfig = new MemoryConfigurationSource { InitialData = initialData }; + var configuration = new WebAssemblyHostConfiguration(); + + // Act + configuration.Add(memoryConfig); + IConfiguration readableConfig = configuration; + var children = readableConfig.GetChildren(); + + // Assert + Assert.NotNull(children); + Assert.NotEmpty(children); + } + + [Fact] + public void CanGetSection() + { + // Arrange + var initialData = new Dictionary<string, string>() { + { "color", "blue" }, + { "type", "car" }, + { "wheels:year", "2008" }, + { "wheels:count", "4" }, + { "wheels:brand", "michelin" }, + { "wheels:brand:type", "rally" }, + }; + var memoryConfig = new MemoryConfigurationSource { InitialData = initialData }; + var configuration = new WebAssemblyHostConfiguration(); + + // Act + configuration.Add(memoryConfig); + var section = configuration.GetSection("wheels").AsEnumerable(makePathsRelative: true).ToDictionary(k => k.Key, v => v.Value); + + // Assert + Assert.Equal(4, section.Count); + Assert.Equal("2008", section["year"]); + Assert.Equal("4", section["count"]); + Assert.Equal("michelin", section["brand"]); + Assert.Equal("rally", section["brand:type"]); + } + + [Fact] + public void CanDisposeProviders() + { + // Arrange + var initialData = new Dictionary<string, string>() { { "color", "blue" } }; + var memoryConfig = new MemoryConfigurationSource { InitialData = initialData }; + var configuration = new WebAssemblyHostConfiguration(); + + // Act + configuration.Add(memoryConfig); + Assert.Equal("blue", configuration["color"]); + var exception = Record.Exception(() => configuration.Dispose()); + + // Assert + Assert.Null(exception); + } + + [Fact] + public void CanSupportDeeplyNestedConfigs() + { + // Arrange + var dic1 = new Dictionary<string, string>() + { + {"Mem1", "Value1"}, + {"Mem1:", "NoKeyValue1"}, + {"Mem1:KeyInMem1", "ValueInMem1"}, + {"Mem1:KeyInMem1:Deep1", "ValueDeep1"} + }; + var dic2 = new Dictionary<string, string>() + { + {"Mem2", "Value2"}, + {"Mem2:", "NoKeyValue2"}, + {"Mem2:KeyInMem2", "ValueInMem2"}, + {"Mem2:KeyInMem2:Deep2", "ValueDeep2"} + }; + var dic3 = new Dictionary<string, string>() + { + {"Mem3", "Value3"}, + {"Mem3:", "NoKeyValue3"}, + {"Mem3:KeyInMem3", "ValueInMem3"}, + {"Mem3:KeyInMem4", "ValueInMem4"}, + {"Mem3:KeyInMem3:Deep3", "ValueDeep3"}, + {"Mem3:KeyInMem3:Deep4", "ValueDeep4"} + }; + var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 }; + var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 }; + var memConfigSrc3 = new MemoryConfigurationSource { InitialData = dic3 }; + var configuration = new WebAssemblyHostConfiguration(); + + // Act + configuration.Add(memConfigSrc1); + configuration.Add(memConfigSrc2); + configuration.Add(memConfigSrc3); + + // Assert + var dict = configuration.GetSection("Mem1").AsEnumerable(makePathsRelative: true).ToDictionary(k => k.Key, v => v.Value); + Assert.Equal(3, dict.Count); + Assert.Equal("NoKeyValue1", dict[""]); + Assert.Equal("ValueInMem1", dict["KeyInMem1"]); + Assert.Equal("ValueDeep1", dict["KeyInMem1:Deep1"]); + + var dict2 = configuration.GetSection("Mem2").AsEnumerable(makePathsRelative: true).ToDictionary(k => k.Key, v => v.Value); + Assert.Equal(3, dict2.Count); + Assert.Equal("NoKeyValue2", dict2[""]); + Assert.Equal("ValueInMem2", dict2["KeyInMem2"]); + Assert.Equal("ValueDeep2", dict2["KeyInMem2:Deep2"]); + + var dict3 = configuration.GetSection("Mem3").AsEnumerable(makePathsRelative: true).ToDictionary(k => k.Key, v => v.Value); + Assert.Equal(5, dict3.Count); + Assert.Equal("NoKeyValue3", dict3[""]); + Assert.Equal("ValueInMem3", dict3["KeyInMem3"]); + Assert.Equal("ValueInMem4", dict3["KeyInMem4"]); + Assert.Equal("ValueDeep3", dict3["KeyInMem3:Deep3"]); + Assert.Equal("ValueDeep4", dict3["KeyInMem3:Deep4"]); + } + + [Fact] + public void NewConfigurationProviderOverridesOldOneWhenKeyIsDuplicated() + { + // Arrange + var dic1 = new Dictionary<string, string>() + { + {"Key1:Key2", "ValueInMem1"} + }; + var dic2 = new Dictionary<string, string>() + { + {"Key1:Key2", "ValueInMem2"} + }; + var memConfigSrc1 = new MemoryConfigurationSource { InitialData = dic1 }; + var memConfigSrc2 = new MemoryConfigurationSource { InitialData = dic2 }; + + var configuration = new WebAssemblyHostConfiguration(); + + // Act + configuration.Add(memConfigSrc1); + configuration.Add(memConfigSrc2); + + // Assert + Assert.Equal("ValueInMem2", configuration["Key1:Key2"]); + } + + private class CustomizedTestConfigurationProvider : ConfigurationProvider + { + public CustomizedTestConfigurationProvider(string key, string value) + => Data.Add(key, value.ToUpper()); + + public override void Set(string key, string value) + { + Data[key] = value; + } + } + + private class CustomizedTestConfigurationSource : IConfigurationSource + { + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + return new CustomizedTestConfigurationProvider("initialKey", "initialValue"); + } + } + } +} diff --git a/src/Components/Blazor/Blazor/test/Hosting/WebAssemblyHostTest.cs b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostTest.cs similarity index 71% rename from src/Components/Blazor/Blazor/test/Hosting/WebAssemblyHostTest.cs rename to src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostTest.cs index b838334566082d1ca5e716414f6a75e09c701e35..71c273c74cbd96d47a72ee921047f3ab8e2e97d6 100644 --- a/src/Components/Blazor/Blazor/test/Hosting/WebAssemblyHostTest.cs +++ b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostTest.cs @@ -4,22 +4,24 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebAssembly.Services; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace Microsoft.AspNetCore.Blazor.Hosting +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting { public class WebAssemblyHostTest { // This won't happen in the product code, but we need to be able to safely call RunAsync // to be able to test a few of the other details. - [Fact] + [Fact] public async Task RunAsync_CanExitBasedOnCancellationToken() { // Arrange - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); var host = builder.Build(); + host.SatelliteResourcesLoader = new TestSatelliteResourcesLoader(); var cts = new CancellationTokenSource(); @@ -36,8 +38,9 @@ namespace Microsoft.AspNetCore.Blazor.Hosting public async Task RunAsync_CallingTwiceCausesException() { // Arrange - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); var host = builder.Build(); + host.SatelliteResourcesLoader = new TestSatelliteResourcesLoader(); var cts = new CancellationTokenSource(); var task = host.RunAsyncCore(cts.Token); @@ -56,9 +59,10 @@ namespace Microsoft.AspNetCore.Blazor.Hosting public async Task DisposeAsync_CanDisposeAfterCallingRunAsync() { // Arrange - var builder = WebAssemblyHostBuilder.CreateDefault(); + var builder = new WebAssemblyHostBuilder(new TestWebAssemblyJSRuntimeInvoker()); builder.Services.AddSingleton<DisposableService>(); var host = builder.Build(); + host.SatelliteResourcesLoader = new TestSatelliteResourcesLoader(); var disposable = host.Services.GetRequiredService<DisposableService>(); @@ -87,5 +91,15 @@ namespace Microsoft.AspNetCore.Blazor.Hosting return new ValueTask(Task.CompletedTask); } } + + private class TestSatelliteResourcesLoader : SatelliteResourcesLoader + { + internal TestSatelliteResourcesLoader() + : base(WebAssemblyJSRuntimeInvoker.Instance) + { + } + + public override ValueTask LoadCurrentCultureResourcesAsync() => default; + } } } diff --git a/src/Components/Blazor/Validation/test/Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj b/src/Components/WebAssembly/WebAssembly/test/Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj similarity index 54% rename from src/Components/Blazor/Validation/test/Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj rename to src/Components/WebAssembly/WebAssembly/test/Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj index 199d86622f6d7acf16dc1acc6623be3e07e345f1..6450d01d8aa0db49109db601ac760d8e9454c0e7 100644 --- a/src/Components/Blazor/Validation/test/Microsoft.AspNetCore.Blazor.DataAnnotations.Validation.Tests.csproj +++ b/src/Components/WebAssembly/WebAssembly/test/Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> @@ -7,9 +7,8 @@ </PropertyGroup> <ItemGroup> - <Reference Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" /> - <!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. --> - <Reference Include="Microsoft.AspNetCore.Components" /> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly" /> + <Reference Include="Microsoft.CodeAnalysis.CSharp" /> </ItemGroup> </Project> diff --git a/src/Components/WebAssembly/WebAssembly/test/TestWebAssemblyJSRuntimeInvoker.cs b/src/Components/WebAssembly/WebAssembly/test/TestWebAssemblyJSRuntimeInvoker.cs new file mode 100644 index 0000000000000000000000000000000000000000..0b8e8b0c16148f567c5577f06f33babf588098c9 --- /dev/null +++ b/src/Components/WebAssembly/WebAssembly/test/TestWebAssemblyJSRuntimeInvoker.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Components.WebAssembly.Services; + +namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting +{ + internal class TestWebAssemblyJSRuntimeInvoker : WebAssemblyJSRuntimeInvoker + { + private readonly string _environment; + + public TestWebAssemblyJSRuntimeInvoker(string environment = "Production") + { + _environment = environment; + } + + public override TResult InvokeUnmarshalled<T0, T1, T2, TResult>(string identifier, T0 arg0, T1 arg1, T2 arg2) + { + switch (identifier) + { + case "Blazor._internal.getApplicationEnvironment": + return (TResult)(object)_environment; + case "Blazor._internal.getConfig": + return (TResult)(object)null; + case "Blazor._internal.navigationManager.getBaseURI": + var testUri = "https://www.example.com/awesome-part-that-will-be-truncated-in-tests"; + return (TResult)(object)testUri; + case "Blazor._internal.navigationManager.getLocationHref": + var testHref = "https://www.example.com/awesome-part-that-will-be-truncated-in-tests/cool"; + return (TResult)(object)testHref; + default: + throw new NotImplementedException($"{nameof(TestWebAssemblyJSRuntimeInvoker)} has no implementation for '{identifier}'."); + } + } + } +} diff --git a/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj b/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj new file mode 100644 index 0000000000000000000000000000000000000000..95ca0b2f04820fa1126765c4f26d2efc4d23deb2 --- /dev/null +++ b/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.csproj @@ -0,0 +1,17 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile> + <TargetFramework>netstandard2.1</TargetFramework> + <PackageId>$(MSBuildProjectName)</PackageId> + <AssemblyName>System.Net.Http.WebAssemblyHttpHandler</AssemblyName> + <AssemblyVersion>0.2.2.0</AssemblyVersion> + <ProduceOnlyReferenceAssembly>true</ProduceOnlyReferenceAssembly> + <StrongNameKeyId>Open</StrongNameKeyId> + <IsProjectReferenceProvider>false</IsProjectReferenceProvider> + </PropertyGroup> + + <ItemGroup> + <NuspecProperty Include="OutputPath=$(OutputPath)" /> + </ItemGroup> + +</Project> diff --git a/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.netstandard2.1.cs b/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.netstandard2.1.cs new file mode 100644 index 0000000000000000000000000000000000000000..0d0dbc1f752694cc92c1f8c1292526ff2806a1d2 --- /dev/null +++ b/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.netstandard2.1.cs @@ -0,0 +1,11 @@ +using System; +using System.Net.Http; + +namespace System.Net.Http +{ + public partial class WebAssemblyHttpHandler : System.Net.Http.HttpMessageHandler + { + public WebAssemblyHttpHandler() { } + protected override System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage> SendAsync(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { throw null; } + } +} diff --git a/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.nuspec b/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.nuspec new file mode 100644 index 0000000000000000000000000000000000000000..fd5ccf21a04e93996a911fa140b170c455f39812 --- /dev/null +++ b/src/Components/WebAssembly/WebAssemblyHttpHandler/src/Microsoft.AspNetCore.Components.WebAssembly.HttpHandler.nuspec @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd"> + <metadata> + $CommonMetadataElements$ + <dependencies> + <group targetFramework=".NETStandard2.1" /> + </dependencies> + </metadata> + <files> + $CommonFileElements$ + <file src="$OutputPath$System.Net.Http.WebAssemblyHttpHandler.dll" target="lib\netstandard2.1\System.Net.Http.WebAssemblyHttpHandler.dll" /> + </files> +</package> diff --git a/src/Components/Blazor/testassets/HostedInAspNet.Client/Home.razor b/src/Components/WebAssembly/testassets/HostedInAspNet.Client/Home.razor similarity index 100% rename from src/Components/Blazor/testassets/HostedInAspNet.Client/Home.razor rename to src/Components/WebAssembly/testassets/HostedInAspNet.Client/Home.razor diff --git a/src/Components/WebAssembly/testassets/HostedInAspNet.Client/HostedInAspNet.Client.csproj b/src/Components/WebAssembly/testassets/HostedInAspNet.Client/HostedInAspNet.Client.csproj new file mode 100644 index 0000000000000000000000000000000000000000..84dae61bf74a5dde7c0f17f0f101a984c91b2239 --- /dev/null +++ b/src/Components/WebAssembly/testassets/HostedInAspNet.Client/HostedInAspNet.Client.csproj @@ -0,0 +1,17 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + <OutputType>Exe</OutputType> + <ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally> + <RazorLangVersion>3.0</RazorLangVersion> + <!-- Disable compression in this project so that we can validate that it can be disabled --> + <BlazorEnableCompression>false</BlazorEnableCompression> + <FixupWebAssemblyHttpHandlerReference>true</FixupWebAssemblyHttpHandlerReference> + </PropertyGroup> + + <ItemGroup> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly" /> + </ItemGroup> + +</Project> diff --git a/src/Components/Blazor/testassets/HostedInAspNet.Client/Program.cs b/src/Components/WebAssembly/testassets/HostedInAspNet.Client/Program.cs similarity index 89% rename from src/Components/Blazor/testassets/HostedInAspNet.Client/Program.cs rename to src/Components/WebAssembly/testassets/HostedInAspNet.Client/Program.cs index e922c2996f75b3674f4fa49b26236bb749d00085..fd106e9719c539a20889907a1c8a9221649b742d 100644 --- a/src/Components/Blazor/testassets/HostedInAspNet.Client/Program.cs +++ b/src/Components/WebAssembly/testassets/HostedInAspNet.Client/Program.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; -using Microsoft.AspNetCore.Blazor.Hosting; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; namespace HostedInAspNet.Client { diff --git a/src/Components/Blazor/testassets/HostedInAspNet.Client/_Imports.razor b/src/Components/WebAssembly/testassets/HostedInAspNet.Client/_Imports.razor similarity index 100% rename from src/Components/Blazor/testassets/HostedInAspNet.Client/_Imports.razor rename to src/Components/WebAssembly/testassets/HostedInAspNet.Client/_Imports.razor diff --git a/src/Components/Blazor/testassets/HostedInAspNet.Client/wwwroot/customJsFileForTests.js b/src/Components/WebAssembly/testassets/HostedInAspNet.Client/wwwroot/customJsFileForTests.js similarity index 100% rename from src/Components/Blazor/testassets/HostedInAspNet.Client/wwwroot/customJsFileForTests.js rename to src/Components/WebAssembly/testassets/HostedInAspNet.Client/wwwroot/customJsFileForTests.js diff --git a/src/Components/WebAssembly/testassets/HostedInAspNet.Client/wwwroot/index.html b/src/Components/WebAssembly/testassets/HostedInAspNet.Client/wwwroot/index.html new file mode 100644 index 0000000000000000000000000000000000000000..643b68bb6bd6c578040791c2bebb3e7e5a49321d --- /dev/null +++ b/src/Components/WebAssembly/testassets/HostedInAspNet.Client/wwwroot/index.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8" /> + <title>Sample Blazor app</title> +</head> +<body> + <app>Loading...</app> + <script src="customJsFileForTests.js"></script> + <script src="_framework/blazor.webassembly.js" autostart="false"></script> + + <!-- + To show we can customize the boot resource loading process, the server looks for these + flags when collecting logs, and E2E tests check the right entries were seen. + --> + <script> + Blazor.start({ + loadBootResource: function (type, name, defaultUri, integrity) { + return type === 'dotnetjs' + ? `${defaultUri}?customizedbootresource=true` + : fetch(defaultUri, { integrity: integrity, cache: 'no-cache', headers: { 'customizedbootresource': 'true' } }); + } + }); + </script> +</body> +</html> diff --git a/src/Components/WebAssembly/testassets/HostedInAspNet.Server/BootResourceRequestLog.cs b/src/Components/WebAssembly/testassets/HostedInAspNet.Server/BootResourceRequestLog.cs new file mode 100644 index 0000000000000000000000000000000000000000..5746a4228d22925028273dccee6089131599e503 --- /dev/null +++ b/src/Components/WebAssembly/testassets/HostedInAspNet.Server/BootResourceRequestLog.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; + +namespace HostedInAspNet.Server +{ + public class BootResourceRequestLog + { + private List<string> _requestPaths = new List<string>(); + + public IReadOnlyCollection<string> RequestPaths => _requestPaths; + + public void AddRequest(HttpRequest request) + { + _requestPaths.Add(request.Path); + } + + public void Clear() + { + _requestPaths.Clear(); + } + } +} diff --git a/src/Components/Blazor/testassets/HostedInAspNet.Server/HostedInAspNet.Server.csproj b/src/Components/WebAssembly/testassets/HostedInAspNet.Server/HostedInAspNet.Server.csproj similarity index 66% rename from src/Components/Blazor/testassets/HostedInAspNet.Server/HostedInAspNet.Server.csproj rename to src/Components/WebAssembly/testassets/HostedInAspNet.Server/HostedInAspNet.Server.csproj index 483ea4e8f48805aa7f0b0c502bf328ec236d54a9..1b45aaf9e98357b5b094115ef56eb1fe7c5b9f77 100644 --- a/src/Components/Blazor/testassets/HostedInAspNet.Server/HostedInAspNet.Server.csproj +++ b/src/Components/WebAssembly/testassets/HostedInAspNet.Server/HostedInAspNet.Server.csproj @@ -2,9 +2,6 @@ <PropertyGroup> <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> - <!-- This is so that we add the FrameworkReference to Microsoft.AspNetCore.App --> - <UseLatestAspNetCoreReference>true</UseLatestAspNetCoreReference> - </PropertyGroup> <ItemGroup> @@ -12,7 +9,10 @@ </ItemGroup> <ItemGroup> - <Reference Include="Microsoft.AspNetCore.Blazor.Server" /> + <Reference Include="Microsoft.AspNetCore" /> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" /> + <Reference Include="Microsoft.AspNetCore.Diagnostics" /> + <Reference Include="Microsoft.Extensions.Hosting" /> <!-- Avoid MSB3277 warnings due to dependencies brought in through Microsoft.AspNetCore.Blazor targeting netstandard2.0. --> <Reference Include="System.Text.Json" /> </ItemGroup> diff --git a/src/Components/Blazor/testassets/HostedInAspNet.Server/Program.cs b/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Program.cs similarity index 75% rename from src/Components/Blazor/testassets/HostedInAspNet.Server/Program.cs rename to src/Components/WebAssembly/testassets/HostedInAspNet.Server/Program.cs index 412cdfac8b9c1d5354fb32712de5999dfc797559..bcb0fcf29b10a7e8e88ef4fb6e5fbc0766f08661 100644 --- a/src/Components/Blazor/testassets/HostedInAspNet.Server/Program.cs +++ b/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Program.cs @@ -17,6 +17,10 @@ namespace HostedInAspNet.Server Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webHostBuilder => { + // We require this line because we run in Production environment + // and static web assets are only on by default during development. + webHostBuilder.UseStaticWebAssets(); + webHostBuilder.UseStartup<Startup>(); }) .Build(); diff --git a/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Properties/launchSettings.json b/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Properties/launchSettings.json new file mode 100644 index 0000000000000000000000000000000000000000..95979790727994852e918a7a46e820babec6b02f --- /dev/null +++ b/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:56500/", + "sslPort": 44347 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "HostedInAspNet.Server": { + "commandName": "Project", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} diff --git a/src/Components/Blazor/testassets/HostedInAspNet.Server/Startup.cs b/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Startup.cs similarity index 56% rename from src/Components/Blazor/testassets/HostedInAspNet.Server/Startup.cs rename to src/Components/WebAssembly/testassets/HostedInAspNet.Server/Startup.cs index 24e99f969113ff7aa19073a516a198f66f7c8162..66b236ddf8edb6f084086d9ac3dd3e1144086e4e 100644 --- a/src/Components/Blazor/testassets/HostedInAspNet.Server/Startup.cs +++ b/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Startup.cs @@ -14,25 +14,39 @@ namespace HostedInAspNet.Server // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { + services.AddSingleton<BootResourceRequestLog>(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, BootResourceRequestLog bootResourceRequestLog) { + app.Use((context, next) => + { + // This is used by E2E tests to verify that the correct resources were fetched, + // and that it was possible to override the loading mechanism + if (context.Request.Query.ContainsKey("customizedbootresource") + || context.Request.Headers.ContainsKey("customizedbootresource") + || context.Request.Path.Value.EndsWith("/blazor.boot.json")) + { + bootResourceRequestLog.AddRequest(context.Request); + } + return next(); + }); + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); - app.UseBlazorDebugging(); + app.UseWebAssemblyDebugging(); } + app.UseBlazorFrameworkFiles(); app.UseStaticFiles(); - app.UseClientSideBlazorFiles<Client.Program>(); app.UseRouting(); app.UseEndpoints(endpoints => { - endpoints.MapFallbackToClientSideBlazor<Client.Program>("index.html"); + endpoints.MapFallbackToFile("index.html"); }); } } diff --git a/src/Components/Blazor/testassets/MonoSanity/MonoSanity.csproj b/src/Components/WebAssembly/testassets/MonoSanity/MonoSanity.csproj similarity index 57% rename from src/Components/Blazor/testassets/MonoSanity/MonoSanity.csproj rename to src/Components/WebAssembly/testassets/MonoSanity/MonoSanity.csproj index 464f63b57c23de94a501aaf7e5840ee186fe8259..4e52642764c495949353719ad64cc363cbdf7b8d 100644 --- a/src/Components/Blazor/testassets/MonoSanity/MonoSanity.csproj +++ b/src/Components/WebAssembly/testassets/MonoSanity/MonoSanity.csproj @@ -2,8 +2,6 @@ <PropertyGroup> <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> - <!-- This is so that we add the FrameworkReference to Microsoft.AspNetCore.App --> - <UseLatestAspNetCoreReference>true</UseLatestAspNetCoreReference> </PropertyGroup> <ItemGroup> @@ -11,7 +9,9 @@ </ItemGroup> <ItemGroup> - <Reference Include="Microsoft.AspNetCore.Blazor.Server" /> + <Reference Include="Microsoft.AspNetCore" /> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" /> + <Reference Include="Microsoft.Extensions.Hosting" /> </ItemGroup> </Project> diff --git a/src/Components/Blazor/testassets/MonoSanity/Program.cs b/src/Components/WebAssembly/testassets/MonoSanity/Program.cs similarity index 75% rename from src/Components/Blazor/testassets/MonoSanity/Program.cs rename to src/Components/WebAssembly/testassets/MonoSanity/Program.cs index d6fc0b8f488356ca2fab6874beb08df290ebdd5d..6481e4d4bc9556375834908e9a5c5c956acb33a2 100644 --- a/src/Components/Blazor/testassets/MonoSanity/Program.cs +++ b/src/Components/WebAssembly/testassets/MonoSanity/Program.cs @@ -17,6 +17,9 @@ namespace MonoSanity Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webHostBuilder => { + // We require this line because we run in Production environment + // and static web assets are only on by default during development. + webHostBuilder.UseStaticWebAssets(); webHostBuilder.UseStartup<Startup>(); }) .Build(); diff --git a/src/Components/Blazor/testassets/MonoSanity/Startup.cs b/src/Components/WebAssembly/testassets/MonoSanity/Startup.cs similarity index 81% rename from src/Components/Blazor/testassets/MonoSanity/Startup.cs rename to src/Components/WebAssembly/testassets/MonoSanity/Startup.cs index 84614a890a25768635806fb6833c4c9fa29c6e30..3cf349e787439ee72295855d430110b341c1f36d 100644 --- a/src/Components/Blazor/testassets/MonoSanity/Startup.cs +++ b/src/Components/WebAssembly/testassets/MonoSanity/Startup.cs @@ -16,12 +16,12 @@ namespace MonoSanity { app.UseDeveloperExceptionPage(); app.UseFileServer(new FileServerOptions() { EnableDefaultFiles = true, }); + app.UseBlazorFrameworkFiles(); app.UseStaticFiles(); - app.UseClientSideBlazorFiles<MonoSanityClient.Program>(); app.UseRouting(); app.UseEndpoints(endpoints => { - endpoints.MapFallbackToClientSideBlazor<MonoSanityClient.Program>("index.html"); + endpoints.MapFallbackToFile("index.html"); }); } } diff --git a/src/Components/Blazor/testassets/MonoSanity/wwwroot/index.html b/src/Components/WebAssembly/testassets/MonoSanity/wwwroot/index.html similarity index 100% rename from src/Components/Blazor/testassets/MonoSanity/wwwroot/index.html rename to src/Components/WebAssembly/testassets/MonoSanity/wwwroot/index.html diff --git a/src/Components/Blazor/testassets/MonoSanity/wwwroot/loader.js b/src/Components/WebAssembly/testassets/MonoSanity/wwwroot/loader.js similarity index 91% rename from src/Components/Blazor/testassets/MonoSanity/wwwroot/loader.js rename to src/Components/WebAssembly/testassets/MonoSanity/wwwroot/loader.js index 328acacdff975cad47c7c2891fbb002a39117872..ad004cee37957334795699cfef2261b42dc8ab2c 100644 --- a/src/Components/Blazor/testassets/MonoSanity/wwwroot/loader.js +++ b/src/Components/WebAssembly/testassets/MonoSanity/wwwroot/loader.js @@ -21,8 +21,8 @@ 'System.dll', 'System.Core.dll', 'System.Net.Http.dll', - 'WebAssembly.Bindings.dll', - 'WebAssembly.Net.Http.dll' + 'System.Net.Http.WebAssemblyHttpHandler.dll', + 'WebAssembly.Bindings.dll' ]); // For these tests we're using Mono's built-in mono_load_runtime_and_bcl util. @@ -110,14 +110,18 @@ } } - function addScriptTagsToDocument() { + async function addScriptTagsToDocument() { var browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate; if (!browserSupportsNativeWebAssembly) { throw new Error('This browser does not support WebAssembly.'); } + var bootJson = await fetch('/_framework/blazor.boot.json').then(res => res.json()); + var dotNetJsResourceName = Object.keys(bootJson.resources.runtime) + .filter(name => name.endsWith('.js')); + var scriptElem = document.createElement('script'); - scriptElem.src = '/_framework/wasm/dotnet.js'; + scriptElem.src = '/_framework/wasm/' + dotNetJsResourceName; document.body.appendChild(scriptElem); } diff --git a/src/Components/Blazor/testassets/MonoSanityClient/Examples.cs b/src/Components/WebAssembly/testassets/MonoSanityClient/Examples.cs similarity index 94% rename from src/Components/Blazor/testassets/MonoSanityClient/Examples.cs rename to src/Components/WebAssembly/testassets/MonoSanityClient/Examples.cs index 1d56128e354687a32b201385a34a1a076019786f..7bae6734495bc399999c02e9d9a021ba648d0893 100644 --- a/src/Components/Blazor/testassets/MonoSanityClient/Examples.cs +++ b/src/Components/WebAssembly/testassets/MonoSanityClient/Examples.cs @@ -58,6 +58,6 @@ namespace MonoSanityClient public static string GetRuntimeInformation() => $"OSDescription: '{RuntimeInformation.OSDescription}';" + $" OSArchitecture: '{RuntimeInformation.OSArchitecture}';" - + $" IsOSPlatform(WEBASSEMBLY): '{RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY"))}'"; + + $" IsOSPlatform(BROWSER): '{RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))}'"; } } diff --git a/src/Components/Blazor/testassets/MonoSanityClient/InternalCalls.cs b/src/Components/WebAssembly/testassets/MonoSanityClient/InternalCalls.cs similarity index 100% rename from src/Components/Blazor/testassets/MonoSanityClient/InternalCalls.cs rename to src/Components/WebAssembly/testassets/MonoSanityClient/InternalCalls.cs diff --git a/src/Components/Blazor/testassets/MonoSanityClient/MonoSanityClient.csproj b/src/Components/WebAssembly/testassets/MonoSanityClient/MonoSanityClient.csproj similarity index 72% rename from src/Components/Blazor/testassets/MonoSanityClient/MonoSanityClient.csproj rename to src/Components/WebAssembly/testassets/MonoSanityClient/MonoSanityClient.csproj index e01c60084327118c093e6374f6d898e16f7297b3..d1e6606fb555c60303cd104cbafe4fa63bc2afe3 100644 --- a/src/Components/Blazor/testassets/MonoSanityClient/MonoSanityClient.csproj +++ b/src/Components/WebAssembly/testassets/MonoSanityClient/MonoSanityClient.csproj @@ -1,9 +1,9 @@ -<Project Sdk="Microsoft.NET.Sdk.Razor" TreatAsLocalProperty="BlazorLinkOnBuild"> +<Project Sdk="Microsoft.NET.Sdk.Razor" TreatAsLocalProperty="BlazorWebAssemblyEnableLinking"> <PropertyGroup> <TargetFramework>netstandard2.1</TargetFramework> <IsPackable>false</IsPackable> - <BlazorLinkOnBuild>false</BlazorLinkOnBuild> + <BlazorWebAssemblyEnableLinking>false</BlazorWebAssemblyEnableLinking> <OutputType>exe</OutputType> <RazorLangVersion>3.0</RazorLangVersion> diff --git a/src/Components/Blazor/testassets/MonoSanityClient/Program.cs b/src/Components/WebAssembly/testassets/MonoSanityClient/Program.cs similarity index 100% rename from src/Components/Blazor/testassets/MonoSanityClient/Program.cs rename to src/Components/WebAssembly/testassets/MonoSanityClient/Program.cs diff --git a/src/Components/Blazor/testassets/StandaloneApp/App.razor b/src/Components/WebAssembly/testassets/StandaloneApp/App.razor similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/App.razor rename to src/Components/WebAssembly/testassets/StandaloneApp/App.razor diff --git a/src/Components/Blazor/testassets/StandaloneApp/Pages/Counter.razor b/src/Components/WebAssembly/testassets/StandaloneApp/Pages/Counter.razor similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/Pages/Counter.razor rename to src/Components/WebAssembly/testassets/StandaloneApp/Pages/Counter.razor diff --git a/src/Components/Blazor/testassets/StandaloneApp/Pages/FetchData.razor b/src/Components/WebAssembly/testassets/StandaloneApp/Pages/FetchData.razor similarity index 96% rename from src/Components/Blazor/testassets/StandaloneApp/Pages/FetchData.razor rename to src/Components/WebAssembly/testassets/StandaloneApp/Pages/FetchData.razor index c640fe9fbc0721bca4bb5c23467ccffb96f688cd..0ed7245e452e64476cb480add5103a5c2aec5580 100644 --- a/src/Components/Blazor/testassets/StandaloneApp/Pages/FetchData.razor +++ b/src/Components/WebAssembly/testassets/StandaloneApp/Pages/FetchData.razor @@ -52,7 +52,7 @@ else protected override async Task OnParametersSetAsync() { startDate = StartDate.GetValueOrDefault(DateTime.Now); - forecasts = await Http.GetJsonAsync<WeatherForecast[]>( + forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>( $"sample-data/weather.json?date={startDate.ToString("yyyy-MM-dd")}"); // Because StandaloneApp doesn't really have a server endpoint to get dynamic data from, diff --git a/src/Components/Blazor/testassets/StandaloneApp/Pages/Index.razor b/src/Components/WebAssembly/testassets/StandaloneApp/Pages/Index.razor similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/Pages/Index.razor rename to src/Components/WebAssembly/testassets/StandaloneApp/Pages/Index.razor diff --git a/src/Components/Blazor/testassets/StandaloneApp/Program.cs b/src/Components/WebAssembly/testassets/StandaloneApp/Program.cs similarity index 65% rename from src/Components/Blazor/testassets/StandaloneApp/Program.cs rename to src/Components/WebAssembly/testassets/StandaloneApp/Program.cs index 8da14834b65956cbfdd433e539f3f16add78f4fb..67e675954a93a1ca9cf9be8bb99b9285137968c1 100644 --- a/src/Components/Blazor/testassets/StandaloneApp/Program.cs +++ b/src/Components/WebAssembly/testassets/StandaloneApp/Program.cs @@ -1,8 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Net.Http; using System.Threading.Tasks; -using Microsoft.AspNetCore.Blazor.Hosting; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.DependencyInjection; namespace StandaloneApp { @@ -12,6 +15,7 @@ namespace StandaloneApp { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("app"); + builder.Services.AddSingleton(new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); await builder.Build().RunAsync(); } diff --git a/src/Components/WebAssembly/testassets/StandaloneApp/Properties/launchSettings.json b/src/Components/WebAssembly/testassets/StandaloneApp/Properties/launchSettings.json new file mode 100644 index 0000000000000000000000000000000000000000..541c4c44680a651d820957d9c38cae51ce988e06 --- /dev/null +++ b/src/Components/WebAssembly/testassets/StandaloneApp/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:56502/", + "sslPort": 44332 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "StandaloneApp": { + "commandName": "Project", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} diff --git a/src/Components/Blazor/testassets/StandaloneApp/Shared/MainLayout.razor b/src/Components/WebAssembly/testassets/StandaloneApp/Shared/MainLayout.razor similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/Shared/MainLayout.razor rename to src/Components/WebAssembly/testassets/StandaloneApp/Shared/MainLayout.razor diff --git a/src/Components/Blazor/testassets/StandaloneApp/Shared/NavMenu.razor b/src/Components/WebAssembly/testassets/StandaloneApp/Shared/NavMenu.razor similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/Shared/NavMenu.razor rename to src/Components/WebAssembly/testassets/StandaloneApp/Shared/NavMenu.razor diff --git a/src/Components/WebAssembly/testassets/StandaloneApp/StandaloneApp.csproj b/src/Components/WebAssembly/testassets/StandaloneApp/StandaloneApp.csproj new file mode 100644 index 0000000000000000000000000000000000000000..8959978daad4156cc6cb1d2e71ef02f6ea05cef7 --- /dev/null +++ b/src/Components/WebAssembly/testassets/StandaloneApp/StandaloneApp.csproj @@ -0,0 +1,32 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + <ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally> + <RazorLangVersion>3.0</RazorLangVersion> + <FixupWebAssemblyHttpHandlerReference>true</FixupWebAssemblyHttpHandlerReference> + </PropertyGroup> + + <ItemGroup> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly" /> + <Reference Include="System.Net.Http.Json" /> + </ItemGroup> + + <!-- A bit of msbuild magic to support reference resolver tests --> + <Target Name="CreateReferenceHintPathsList" AfterTargets="ResolveAssemblyReferences"> + <ItemGroup> + <_BclFile Include="$(ComponentsWebAssemblyBaseClassLibraryPath)*" /> + <_BclFile Include="$(ComponentsWebAssemblyBaseClassLibraryFacadesPath)*" /> + <_BclFile Include="$(ComponentsWebAssemblyFrameworkPath)*" /> + </ItemGroup> + + <WriteLinesToFile Lines="@(ReferencePath);@(ReferenceDependencyPaths)" File="$(IntermediateOutputPath)referenceHints.txt" WriteOnlyWhenDifferent="true" Overwrite="true" /> + <WriteLinesToFile Lines="@(_BclFile)" File="$(IntermediateOutputPath)bclLocations.txt" WriteOnlyWhenDifferent="true" Overwrite="true" /> + + <ItemGroup> + <EmbeddedResource Include="$(IntermediateOutputPath)bclLocations.txt" Link="bclLocations.txt" Type="Non-Resx" /> + <EmbeddedResource Include="$(IntermediateOutputPath)referenceHints.txt" Link="referenceHints.txt" Type="Non-Resx" /> + </ItemGroup> + </Target> + +</Project> diff --git a/src/Components/Blazor/testassets/StandaloneApp/_Imports.razor b/src/Components/WebAssembly/testassets/StandaloneApp/_Imports.razor similarity index 85% rename from src/Components/Blazor/testassets/StandaloneApp/_Imports.razor rename to src/Components/WebAssembly/testassets/StandaloneApp/_Imports.razor index efbdfe11c5860d91432f9e3ac89c3166178fcb9b..99ac4c259688bc0ff5092927d978bdcdb09d7b1b 100644 --- a/src/Components/Blazor/testassets/StandaloneApp/_Imports.razor +++ b/src/Components/WebAssembly/testassets/StandaloneApp/_Imports.razor @@ -1,4 +1,5 @@ @using System.Net.Http +@using System.Net.Http.Json @using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Web @using StandaloneApp diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/site.css b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/app.css similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/site.css rename to src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/app.css diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/bootstrap/bootstrap.min.css b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/bootstrap/bootstrap.min.css similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/bootstrap/bootstrap.min.css rename to src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/bootstrap/bootstrap.min.css diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/bootstrap/bootstrap.min.css.map b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/bootstrap/bootstrap.min.css.map similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/bootstrap/bootstrap.min.css.map rename to src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/bootstrap/bootstrap.min.css.map diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/FONT-LICENSE b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/FONT-LICENSE similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/FONT-LICENSE rename to src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/FONT-LICENSE diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/ICON-LICENSE b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/ICON-LICENSE similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/ICON-LICENSE rename to src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/ICON-LICENSE diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/README.md b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/README.md similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/README.md rename to src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/README.md diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css rename to src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.eot b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.eot similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.eot rename to src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.eot diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.otf b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.otf similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.otf rename to src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.otf diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.svg b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.svg similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.svg rename to src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.svg diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf rename to src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.woff b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.woff similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.woff rename to src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/css/open-iconic/font/fonts/open-iconic.woff diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/index.html b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/index.html similarity index 93% rename from src/Components/Blazor/testassets/StandaloneApp/wwwroot/index.html rename to src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/index.html index 646ea2f3da09cbc604445ddf8ff27bba71fa8469..8828febac2bf82a220ce187c0c9a1f40d106a6b9 100644 --- a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/index.html +++ b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/index.html @@ -7,7 +7,7 @@ <title>Blazor standalone</title> <base href="/" /> <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" /> - <link href="css/site.css" rel="stylesheet" /> + <link href="css/app.css" rel="stylesheet" /> </head> <body> <app>Loading...</app> diff --git a/src/Components/Blazor/testassets/StandaloneApp/wwwroot/sample-data/weather.json b/src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/sample-data/weather.json similarity index 100% rename from src/Components/Blazor/testassets/StandaloneApp/wwwroot/sample-data/weather.json rename to src/Components/WebAssembly/testassets/StandaloneApp/wwwroot/sample-data/weather.json diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/App.razor b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/App.razor new file mode 100644 index 0000000000000000000000000000000000000000..d700070807f1f9fba7e7551d8fdf7d9aa0d635d3 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/App.razor @@ -0,0 +1,16 @@ +<CascadingAuthenticationState> + <Router AppAssembly="@typeof(Program).Assembly"> + <Found Context="routeData"> + <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"> + <NotAuthorized> + <RedirectToLogin /> + </NotAuthorized> + </AuthorizeRouteView> + </Found> + <NotFound> + <LayoutView Layout="@typeof(MainLayout)"> + <p>Sorry, there's nothing at this address.</p> + </LayoutView> + </NotFound> + </Router> +</CascadingAuthenticationState> diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/OidcAccount.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/OidcAccount.cs new file mode 100644 index 0000000000000000000000000000000000000000..b6d39ef9e69a9ff7e8440e2577f903ef5e16880c --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/OidcAccount.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; + +namespace Wasm.Authentication.Client +{ + public class OidcAccount : RemoteUserAccount + { + [JsonPropertyName("amr")] + public string[] AuthenticationMethod { get; set; } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/AdminSettings.razor b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/AdminSettings.razor new file mode 100644 index 0000000000000000000000000000000000000000..573072a3ee53b3232d94c4580b37140649917113 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/AdminSettings.razor @@ -0,0 +1,47 @@ +@page "/admin-settings" +@attribute [Authorize(Roles = "admin")] +@inject IAccessTokenProvider TokenProvider +@inject NavigationManager Navigation + +@if(_error == null) +{ + <button id="admin-action" @onclick="AdminAction">Perform admin action</button> +} +else if(_error == true) +{ + <p>Could not get the access token.</p> +} +else if (_error == false) +{ + <p id="admin-success">Successfully perfomed admin action.</p> +} + +@code { + + private bool? _error; + + public async Task AdminAction() + { + var tokenResult = await TokenProvider.RequestAccessToken(); + + if (tokenResult.TryGetToken(out var token)) + { + var client = new HttpClient() { BaseAddress = new Uri(Navigation.BaseUri) }; + var request = new HttpRequestMessage(HttpMethod.Post, "Roles/AdminOnly"); + request.Headers.Add("Authorization", $"Bearer {token.Value}"); + var response = await client.SendAsync(request); + if (response.StatusCode != System.Net.HttpStatusCode.OK) + { + _error = true; + } + else + { + _error = false; + } + } + else + { + _error = true; + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/Authentication.razor b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/Authentication.razor new file mode 100644 index 0000000000000000000000000000000000000000..06bbc96eb4a7dcc9dfb96fb5acc44d7b91b7cd7d --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/Authentication.razor @@ -0,0 +1,43 @@ +@page "/authentication/{action}" +@inject StateService State +@inject AuthenticationStateProvider AuthenticationStateProvider +@inject NavigationManager Navigation + +<RemoteAuthenticatorViewCore TAuthenticationState="RemoteAppState" + AuthenticationState="AppState" + OnLogInSucceeded="CompleteLogin" + Action="@Action" /> + +@code{ + [Parameter] public string Action { get; set; } + + public RemoteAppState AppState { get; set; } = new RemoteAppState(); + + protected override void OnInitialized() + { + if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogIn, Action)) + { + AppState.State = State.GetCurrentState(); + } + + base.OnInitialized(); + } + + public async Task CompleteLogin(RemoteAppState remoteState) + { + if (RemoteAuthenticationActions.IsAction(RemoteAuthenticationActions.LogInCallback, Action)) + { + State.RestoreCurrentState(remoteState.State); + } + + var userState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + var user = userState.User; + + if (user.HasClaim("NewUser", "true")) + { + var originalReturnUrl = remoteState.ReturnUrl; + var preferencesUrl = Navigation.ToAbsoluteUri("preferences"); + remoteState.ReturnUrl = $"{preferencesUrl}?returnUrl={Uri.EscapeDataString(originalReturnUrl)}"; + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/FetchData.razor b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/FetchData.razor new file mode 100644 index 0000000000000000000000000000000000000000..2967c904deb376fa10216a4e43b3a5fccd3bb45c --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/FetchData.razor @@ -0,0 +1,58 @@ +@page "/fetchdata" +@using Wasm.Authentication.Shared +@implements IDisposable +@attribute [Authorize] +@inject WeatherForecastClient WeatherForecast +<h1>Weather forecast</h1> + +<p>This component demonstrates fetching data from the server.</p> + +@if (forecasts == null) +{ + <p><em>Loading...</em></p> +} +else +{ + <table class="table"> + <thead> + <tr> + <th>Date</th> + <th>Temp. (C)</th> + <th>Temp. (F)</th> + <th>Summary</th> + </tr> + </thead> + <tbody> + @foreach (var forecast in forecasts) + { + <tr> + <td>@forecast.Date.ToShortDateString()</td> + <td>@forecast.TemperatureC</td> + <td>@forecast.TemperatureF</td> + <td>@forecast.Summary</td> + </tr> + } + </tbody> + </table> +} + +@code { + private WeatherForecast[] forecasts; + + protected override async Task OnInitializedAsync() + { + try + { + forecasts = await WeatherForecast.GetForecastAsync(); + } + catch (AccessTokenNotAvailableException exception) + { + exception.Redirect(); + } + } + + public void Dispose() + { + WeatherForecast.Dispose(); + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/Index.razor b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/Index.razor new file mode 100644 index 0000000000000000000000000000000000000000..1f55ad41441e20eb39eca11de0a238c6fab3ebf3 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/Index.razor @@ -0,0 +1,27 @@ +@page "/" +@inject StateService State +@inject IJSRuntime JS +<h1>Hello, world!</h1> + +Welcome to your new app. + +Current state is: +<p id="app-state">@State.GetCurrentState()</p> + +<!-- Elements to help testing functionality --> +<p id="test-helpers"> + <button id="test-clear-storage" @onclick="ClearStorage">Clear storage</button> + <button id="test-refresh-page" @onclick="TriggerPageRefresh">Refresh page</button> +</p> + +@code{ + public async Task ClearStorage() + { + await JS.InvokeVoidAsync("sessionStorage.clear"); + } + + public async Task TriggerPageRefresh() + { + await JS.InvokeVoidAsync("location.reload", true); + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/MakeAdmin.razor b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/MakeAdmin.razor new file mode 100644 index 0000000000000000000000000000000000000000..e1f23f85320fcbe85f3449cf36180d94471f1bb9 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/MakeAdmin.razor @@ -0,0 +1,37 @@ +@page "/new-admin" +@attribute [Authorize] +@inject IAccessTokenProvider TokenProvider +@inject NavigationManager Navigation + +@if (_error == true) +{ + <p>Could not get the access token.</p> +} +else if (_error == false) +{ + <p>Successfully added to the admin group.</p> +} + +@code { + + private bool? _error; + + protected override async Task OnInitializedAsync() + { + var tokenResult = await TokenProvider.RequestAccessToken(); + + if (tokenResult.TryGetToken(out var token)) + { + var client = new HttpClient() { BaseAddress = new Uri(Navigation.BaseUri) }; + var request = new HttpRequestMessage(HttpMethod.Post, "Roles/MakeAdmin"); + request.Headers.Add("Authorization", $"Bearer {token.Value}"); + var response = await client.SendAsync(request); + response.EnsureSuccessStatusCode(); + + } + else + { + _error = true; + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/User.razor b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/User.razor new file mode 100644 index 0000000000000000000000000000000000000000..010276ffb488c9683fb6a79539f89c74c4ac4ec5 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/User.razor @@ -0,0 +1,70 @@ +@page "/User" +@attribute [Authorize] +@using System.Text.Json +@using System.Security.Claims +@inject IAccessTokenProvider AuthorizationService + +<h1>Welcome @AuthenticatedUser?.Identity?.Name</h1> + +<h2>Claims for the user</h2> +@foreach (var claim in AuthenticatedUser?.Claims ?? Array.Empty<Claim>()) +{ + <p class="claim">@(claim.Type): @claim.Value</p> +} + +<h2>Access token for the user</h2> +<p id="access-token">@AccessToken?.Value</p> + +<h2>Access token claims</h2> +@foreach (var claim in GetAccessTokenClaims()) +{ + <p>@(claim.Key): @claim.Value.ToString()</p> +} + +@if (AccessToken != null) +{ + <h2>Access token expires</h2> + <p>Current time: <span id="current-time">@DateTimeOffset.Now</span></p> + <p id="access-token-expires">@AccessToken.Expires</p> + + <h2>Access token granted scopes (as reported by the API)</h2> + @foreach (var scope in AccessToken.GrantedScopes) + { + <p>Scope: @scope</p> + } +} + +@code { + [CascadingParameter] private Task<AuthenticationState> AuthenticationState { get; set; } + + public ClaimsPrincipal AuthenticatedUser { get; set; } + public AccessToken AccessToken { get; set; } + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + var state = await AuthenticationState; + var accessTokenResult = await AuthorizationService.RequestAccessToken(); + if (!accessTokenResult.TryGetToken(out var token)) + { + throw new InvalidOperationException("Failed to provision the access token."); + } + + AccessToken = token; + + AuthenticatedUser = state.User; + } + + protected IDictionary<string, object> GetAccessTokenClaims() + { + if (AccessToken == null) + { + return new Dictionary<string, object>(); + } + + // header.payload.signature + var payload = AccessToken.Value.Split(".")[1]; + var base64Payload = payload.Replace('-', '+').Replace('_', '/').PadRight(payload.Length + (4 - payload.Length % 4) % 4, '='); + return JsonSerializer.Deserialize<IDictionary<string, object>>(Convert.FromBase64String(base64Payload)); + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/UserPreferences.razor b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/UserPreferences.razor new file mode 100644 index 0000000000000000000000000000000000000000..978e1107bb2ebe2b95815f22632164aa14196b25 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Pages/UserPreferences.razor @@ -0,0 +1,47 @@ +@page "/preferences" +@attribute [Authorize] +@using System.Text.Json; +@using System.Text; +@using System.Net.Http.Headers; + +@inject NavigationManager Navigation +@inject IAccessTokenProvider AccessTokenProvider + +<p>User preferences</p> + +<input id="color-preference" type="text" @bind="Color" /> +<button id="submit-preference" @onclick="SendPreferences">Send</button> + +@code { + public string Color { get; set; } + + public async Task SendPreferences() + { + var content = new StringContent(JsonSerializer.Serialize(new UserPreferences { Color = Color }), Encoding.UTF8, "application/json"); + var tokenResponse = await AccessTokenProvider.RequestAccessToken(); + if (tokenResponse.TryGetToken(out var token)) + { + var client = new HttpClient { BaseAddress = new Uri(Navigation.BaseUri) }; + var request = new HttpRequestMessage(HttpMethod.Post, "Preferences/AddPreferences"); + request.Content = content; + + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Value); + + var response = await client.SendAsync(request); + if (response.IsSuccessStatusCode) + { + var query = new Uri(Navigation.Uri).Query; + var hasReturnUrl = System.Text.RegularExpressions.Regex.Match(query, ".*?returnUrl=([^&]+).*"); + if (hasReturnUrl.Success) + { + var returnUrl = hasReturnUrl.Groups[1]; + Navigation.NavigateTo(Uri.UnescapeDataString(returnUrl.Value)); + } + } + else + { + Navigation.NavigateTo("/"); + } + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/PreferencesUserFactory.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/PreferencesUserFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..f9971712fc2f48d54afa5ff4d9f37f370e9e1410 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/PreferencesUserFactory.cs @@ -0,0 +1,62 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; + +namespace Wasm.Authentication.Client +{ + public class PreferencesUserFactory : AccountClaimsPrincipalFactory<OidcAccount> + { + private readonly HttpClient _httpClient; + + public PreferencesUserFactory(NavigationManager navigationManager, IAccessTokenProviderAccessor accessor) + : base(accessor) + { + _httpClient = new HttpClient { BaseAddress = new Uri(navigationManager.BaseUri) }; + } + + public async override ValueTask<ClaimsPrincipal> CreateUserAsync( + OidcAccount account, + RemoteAuthenticationUserOptions options) + { + var initialUser = await base.CreateUserAsync(account, options); + + if (initialUser.Identity.IsAuthenticated) + { + foreach (var value in account.AuthenticationMethod) + { + ((ClaimsIdentity)initialUser.Identity).AddClaim(new Claim("amr", value)); + } + + var tokenResponse = await TokenProvider.RequestAccessToken(); + if (tokenResponse.TryGetToken(out var token)) + { + var request = new HttpRequestMessage(HttpMethod.Get, "Preferences/HasCompletedAdditionalInformation"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Value); + + var response = await _httpClient.SendAsync(request); + if (response.StatusCode != HttpStatusCode.OK) + { + throw new InvalidOperationException("Error accessing additional user info."); + } + + var hasInfo = JsonSerializer.Deserialize<bool>(await response.Content.ReadAsStringAsync()); + if (!hasInfo) + { + // The actual pattern would be to cache this info to avoid constant queries to the server per auth update. + // (By default once every minute) + ((ClaimsIdentity)initialUser.Identity).AddClaim(new Claim("NewUser", "true")); + } + } + } + + return initialUser; + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Program.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Program.cs new file mode 100644 index 0000000000000000000000000000000000000000..0efb670f10e36f70b2696cd1f3774c6acfbbed54 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Program.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.DependencyInjection; + +namespace Wasm.Authentication.Client +{ + public class Program + { + public static async Task Main(string[] args) + { + var builder = WebAssemblyHostBuilder.CreateDefault(args); + + builder.Services.AddApiAuthorization<RemoteAppState, OidcAccount>() + .AddAccountClaimsPrincipalFactory<RemoteAppState, OidcAccount, PreferencesUserFactory>(); + + builder.Services.AddHttpClient<WeatherForecastClient>(client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) + .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>(); + + builder.Services.AddSingleton<StateService>(); + + builder.RootComponents.Add<App>("app"); + + await builder.Build().RunAsync(); + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/RemoteAppState.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/RemoteAppState.cs new file mode 100644 index 0000000000000000000000000000000000000000..eee8535069b330935ed12127c66832bbe9cbb2a0 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/RemoteAppState.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; + +namespace Wasm.Authentication.Client +{ + public class RemoteAppState : RemoteAuthenticationState + { + public string State { get; set; } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Shared/LoginDisplay.razor b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Shared/LoginDisplay.razor new file mode 100644 index 0000000000000000000000000000000000000000..be15afdfe8b08e3c20b323a48cea22f4173bc27e --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Shared/LoginDisplay.razor @@ -0,0 +1,22 @@ +@using Microsoft.AspNetCore.Components.Authorization +@inject NavigationManager Navigation +@inject SignOutSessionStateManager SignOutManager + +<AuthorizeView Context="authenticationState"> + <Authorized> + <a href="authentication/profile">Hello, @authenticationState.User.Identity.Name!</a> + <button class="nav-link btn btn-link" @onclick="BeginSignOut">Log out</button> + </Authorized> + <NotAuthorized> + <a href="authentication/register">Register</a> + <a href="authentication/login">Log in</a> + </NotAuthorized> +</AuthorizeView> + +@code{ + public async Task BeginSignOut() + { + await SignOutManager.SetSignOutState(); + Navigation.NavigateTo("authentication/logout"); + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Shared/MainLayout.razor b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Shared/MainLayout.razor new file mode 100644 index 0000000000000000000000000000000000000000..48e3fce6ef7fa753933ada598ea94a8070cd2f53 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Shared/MainLayout.razor @@ -0,0 +1,16 @@ +@inherits LayoutComponentBase + +<div class="sidebar"> + <NavMenu /> +</div> + +<div class="main"> + <div class="top-row px-4 auth"> + <LoginDisplay /> + <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a> + </div> + + <div class="content px-4"> + @Body + </div> +</div> \ No newline at end of file diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Shared/NavMenu.razor b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Shared/NavMenu.razor new file mode 100644 index 0000000000000000000000000000000000000000..b401588333fee90c372be05f779d08d85b8b459b --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Shared/NavMenu.razor @@ -0,0 +1,53 @@ +<div class="top-row pl-4 navbar navbar-dark"> + <a class="navbar-brand" href="">Wasm.Authentication.Client</a> + <button class="navbar-toggler" @onclick="ToggleNavMenu"> + <span class="navbar-toggler-icon"></span> + </button> +</div> + +<div class="@NavMenuCssClass" @onclick="ToggleNavMenu"> + <ul class="nav flex-column"> + <li class="nav-item px-3"> + <NavLink class="nav-link" href="" Match="NavLinkMatch.All"> + <span class="oi oi-home" aria-hidden="true"></span> Home + </NavLink> + </li> + <li class="nav-item px-3"> + <NavLink class="nav-link" href="counter"> + <span class="oi oi-plus" aria-hidden="true"></span> Counter + </NavLink> + </li> + <li class="nav-item px-3"> + <NavLink class="nav-link" href="fetchdata"> + <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data + </NavLink> + </li> + <li class="nav-item px-3"> + <NavLink class="nav-link" href="user"> + <span class="oi oi-list-rich" aria-hidden="true"></span> User + </NavLink> + </li> + <li class="nav-item px-3"> + <NavLink class="nav-link" href="new-admin"> + <span class="oi oi-list-rich" aria-hidden="true"></span> Make admin + </NavLink> + </li> + <li class="nav-item px-3"> + <NavLink class="nav-link" href="admin-settings"> + <span class="oi oi-list-rich" aria-hidden="true"></span> Settings + </NavLink> + </li> + + </ul> +</div> + +@code { + private bool collapseNavMenu = true; + + private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; + + private void ToggleNavMenu() + { + collapseNavMenu = !collapseNavMenu; + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Shared/RedirectToLogin.razor b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Shared/RedirectToLogin.razor new file mode 100644 index 0000000000000000000000000000000000000000..56de7995d3bf528091f993ddbf6eddca64217a40 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Shared/RedirectToLogin.razor @@ -0,0 +1,10 @@ +@inject NavigationManager Navigation +@using Microsoft.Extensions.Options +@inject IOptions<RemoteAuthenticationOptions<ApiAuthorizationProviderOptions>> Options +@code { + + protected override void OnInitialized() + { + Navigation.NavigateTo($"{Options.Value.AuthenticationPaths.LogInPath}?returnUrl={Uri.EscapeDataString(Navigation.Uri)}"); + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/StateService.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/StateService.cs new file mode 100644 index 0000000000000000000000000000000000000000..0906ee85b99779f89a5975b55bbf30dedbf83f02 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/StateService.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Wasm.Authentication.Client +{ + public class StateService + { + private string _state; + + public string GetCurrentState() => _state ??= Guid.NewGuid().ToString(); + + public void RestoreCurrentState(string state) => _state = state; + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Wasm.Authentication.Client.csproj b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Wasm.Authentication.Client.csproj new file mode 100644 index 0000000000000000000000000000000000000000..bd2ecad9e440ab04f1bbff40346e3b2844c326c6 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/Wasm.Authentication.Client.csproj @@ -0,0 +1,24 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + <RazorLangVersion>3.0</RazorLangVersion> + <ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally> + <FixupWebAssemblyHttpHandlerReference>true</FixupWebAssemblyHttpHandlerReference> + <!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. --> + <CompileUsingReferenceAssemblies>false</CompileUsingReferenceAssemblies> + </PropertyGroup> + + <ItemGroup> + <Reference Include="System.Net.Http.Json" /> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly" /> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" /> + <Reference Include="Microsoft.Extensions.Http" /> + + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Wasm.Authentication.Shared\Wasm.Authentication.Shared.csproj" /> + </ItemGroup> + +</Project> diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/WeatherForecastClient.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/WeatherForecastClient.cs new file mode 100644 index 0000000000000000000000000000000000000000..0f9ff86f07716e680a00511d00c2a9153765f13a --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/WeatherForecastClient.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Json; +using System.Threading; +using System.Threading.Tasks; +using Wasm.Authentication.Shared; + +namespace Wasm.Authentication.Client +{ + public class WeatherForecastClient : IDisposable + { + private readonly HttpClient _client; + private readonly CancellationTokenSource _cts = new CancellationTokenSource(); + + public WeatherForecastClient(HttpClient client) + { + _client = client; + } + + public Task<WeatherForecast[]> GetForecastAsync() => + _client.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast", _cts.Token); + + public void Dispose() + { + _client?.Dispose(); + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/_Imports.razor b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/_Imports.razor new file mode 100644 index 0000000000000000000000000000000000000000..8e3d12c353a4971d6657d160c4bafdbc1f750172 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/_Imports.razor @@ -0,0 +1,12 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.JSInterop +@using Wasm.Authentication.Client +@using Wasm.Authentication.Client.Shared +@using Wasm.Authentication.Shared +@using Microsoft.AspNetCore.Authorization diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/wwwroot/css/app.css b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/wwwroot/css/app.css new file mode 100644 index 0000000000000000000000000000000000000000..81050324f236ab8ca34b38c1cc092612161af1a6 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/wwwroot/css/app.css @@ -0,0 +1,181 @@ +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +a, .btn-link { + color: #0366d6; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +app { + position: relative; + display: flex; + flex-direction: column; +} + +.top-row { + height: 3.5rem; + display: flex; + align-items: center; +} + +.main { + flex: 1; +} + + .main .top-row { + background-color: #f7f7f7; + border-bottom: 1px solid #d6d5d5; + justify-content: flex-end; + } + + .main .top-row > a, .main .top-row .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + } + +.main .top-row a:first-child { + overflow: hidden; + text-overflow: ellipsis; +} + +.sidebar { + background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +} + + .sidebar .top-row { + background-color: rgba(0,0,0,0.4); + } + + .sidebar .navbar-brand { + font-size: 1.1rem; + } + + .sidebar .oi { + width: 2rem; + font-size: 1.1rem; + vertical-align: text-top; + top: -2px; + } + + .sidebar .nav-item { + font-size: 0.9rem; + padding-bottom: 0.5rem; + } + + .sidebar .nav-item:first-of-type { + padding-top: 1rem; + } + + .sidebar .nav-item:last-of-type { + padding-bottom: 1rem; + } + + .sidebar .nav-item a { + color: #d7d7d7; + border-radius: 4px; + height: 3rem; + display: flex; + align-items: center; + line-height: 3rem; + } + + .sidebar .nav-item a.active { + background-color: rgba(255,255,255,0.25); + color: white; + } + + .sidebar .nav-item a:hover { + background-color: rgba(255,255,255,0.1); + color: white; + } + +.content { + padding-top: 1.1rem; +} + +.navbar-toggler { + background-color: rgba(255, 255, 255, 0.1); +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid red; +} + +.validation-message { + color: red; +} + +#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; +} + +@media (max-width: 767.98px) { + .main .top-row:not(.auth) { + display: none; + } + + .main .top-row.auth { + justify-content: space-between; + } + + .main .top-row a, .main .top-row .btn-link { + margin-left: 0; + } +} + +@media (min-width: 768px) { + app { + flex-direction: row; + } + + .sidebar { + width: 250px; + height: 100vh; + position: sticky; + top: 0; + } + + .main .top-row { + position: sticky; + top: 0; + } + + .main > div { + padding-left: 2rem !important; + padding-right: 1.5rem !important; + } + + .navbar-toggler { + display: none; + } + + .sidebar .collapse { + /* Never collapse the sidebar for wide screens */ + display: block; + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/bootstrap/bootstrap.min.css b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/wwwroot/css/bootstrap/bootstrap.min.css similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/bootstrap/bootstrap.min.css rename to src/Components/WebAssembly/testassets/Wasm.Authentication.Client/wwwroot/css/bootstrap/bootstrap.min.css diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/index.html b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/wwwroot/index.html similarity index 69% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/index.html rename to src/Components/WebAssembly/testassets/Wasm.Authentication.Client/wwwroot/index.html index 7c402d8073716addf06f6d17e1376ad5caab73f9..93beb842d54c632b6fb748001e3eb8c69a994e52 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/index.html +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Client/wwwroot/index.html @@ -4,10 +4,10 @@ <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> - <title>BlazorWasm-CSharp</title> + <title>Wasm.Authentication.Client</title> <base href="/" /> <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" /> - <link href="css/site.css" rel="stylesheet" /> + <link href="css/app.css" rel="stylesheet" /> </head> <body> @@ -18,7 +18,7 @@ <a href="" class="reload">Reload</a> <a class="dismiss">🗙</a> </div> + <script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script> <script src="_framework/blazor.webassembly.js"></script> </body> - </html> diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/.gitignore b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ed953ea502baafbc56f488331f021b4eddea9a42 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/.gitignore @@ -0,0 +1 @@ +app.db diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Controllers/OidcConfigurationController.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Controllers/OidcConfigurationController.cs new file mode 100644 index 0000000000000000000000000000000000000000..2a3d084e045e107731fffe285e27160840a3d2b2 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Controllers/OidcConfigurationController.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Wasm.Authentication.Server.Controllers +{ + public class OidcConfigurationController : Controller + { + public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger<OidcConfigurationController> logger) + { + ClientRequestParametersProvider = clientRequestParametersProvider; + } + + public IClientRequestParametersProvider ClientRequestParametersProvider { get; } + + [HttpGet("_configuration/{clientId}")] + public IActionResult GetClientRequestParameters([FromRoute]string clientId) + { + var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId); + return Ok(parameters); + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Controllers/PreferencesController.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Controllers/PreferencesController.cs new file mode 100644 index 0000000000000000000000000000000000000000..dd9d703e88e5cfc9bb206b189d6d1028e7a935a5 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Controllers/PreferencesController.cs @@ -0,0 +1,54 @@ +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore.Internal; +using Wasm.Authentication.Server.Data; +using Wasm.Authentication.Server.Models; + +namespace Wasm.Authentication.Server.Controllers +{ + [ApiController] + [Authorize] + public class PreferencesController : ControllerBase + { + private readonly ApplicationDbContext _context; + + public PreferencesController(ApplicationDbContext context) + { + _context = context; + } + + [HttpGet("[controller]/[action]")] + public IActionResult HasCompletedAdditionalInformation() + { + var id = User.FindFirst(ClaimTypes.NameIdentifier); + if (!_context.UserPreferences.Where(u => u.ApplicationUserId == id.Value).Any()) + { + return Ok(false); + } + else + { + return Ok(true); + } + } + + [HttpPost("[controller]/[action]")] + public async Task<IActionResult> AddPreferences([FromBody] UserPreference preferences) + { + var id = User.FindFirst(ClaimTypes.NameIdentifier); + if (!_context.UserPreferences.Where(u => u.ApplicationUserId == id.Value).Any()) + { + preferences.ApplicationUserId = id.Value; + _context.UserPreferences.Add(preferences); + await _context.SaveChangesAsync(); + return Ok(); + } + else + { + return BadRequest(); + } + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Controllers/RolesController.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Controllers/RolesController.cs new file mode 100644 index 0000000000000000000000000000000000000000..10ec3c59e39328197202a5f18e69cf09c2a46ede --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Controllers/RolesController.cs @@ -0,0 +1,56 @@ +using System; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Wasm.Authentication.Server.Models; + +namespace Wasm.Authentication.Server.Controllers +{ + [Authorize] + public class RolesController : Controller + { + private readonly UserManager<ApplicationUser> _userManager; + private readonly RoleManager<IdentityRole> _roleManager; + private readonly IOptions<IdentityOptions> _options; + + public RolesController( + UserManager<ApplicationUser> userManager, + RoleManager<IdentityRole> roleManager, + IOptions<IdentityOptions> options) + { + _userManager = userManager; + _roleManager = roleManager; + _options = options; + } + + [HttpPost("[controller]/[action]")] + public async Task<IActionResult> MakeAdmin() + { + var admin = await _roleManager.FindByNameAsync("admin"); + if (admin == null) + { + await _roleManager.CreateAsync(new IdentityRole { Name = "admin" }); + } + + var id = User.FindFirst(ClaimTypes.NameIdentifier); + if (id == null) + { + return BadRequest(); + } + var currentUser = await _userManager.FindByIdAsync(id.Value); + await _userManager.AddToRoleAsync(currentUser, "admin"); + + return Ok(); + } + + [HttpPost("[controller]/[action]")] + [Authorize(Roles = "admin")] + public IActionResult AdminOnly() + { + return Ok(); + } + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Controllers/WeatherForecastController.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Controllers/WeatherForecastController.cs similarity index 88% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Controllers/WeatherForecastController.cs rename to src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Controllers/WeatherForecastController.cs index 6862c16a71b3753dbc24d2cbd0757a23d0ad5023..9fefb88335c0842e099fcf436c740b99f77b5ac8 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Controllers/WeatherForecastController.cs +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Controllers/WeatherForecastController.cs @@ -1,14 +1,15 @@ -using BlazorWasm_CSharp.Shared; using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using Wasm.Authentication.Shared; -namespace BlazorWasm_CSharp.Server.Controllers +namespace Wasm.Authentication.Server.Controllers { [ApiController] + [Authorize] [Route("[controller]")] public class WeatherForecastController : ControllerBase { diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Data/ApplicationDbContext.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Data/ApplicationDbContext.cs new file mode 100644 index 0000000000000000000000000000000000000000..dbb7e235131da34c5146339b46003c6f2d8cde46 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Data/ApplicationDbContext.cs @@ -0,0 +1,33 @@ +using Wasm.Authentication.Server.Models; +using IdentityServer4.EntityFramework.Options; +using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; + +namespace Wasm.Authentication.Server.Data +{ + public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser> + { + public ApplicationDbContext( + DbContextOptions options, + IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions) + { + } + + public DbSet<UserPreference> UserPreferences { get; set; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity<ApplicationUser>().HasOne(u => u.UserPreference); + + builder.Entity<UserPreference>() + .Property(u => u.Id).ValueGeneratedOnAdd(); + + builder.Entity<UserPreference>() + .HasKey(p => p.Id); + + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Data/Migrations/20200324213904_Initial.Designer.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Data/Migrations/20200324213904_Initial.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..5a9de57b6de8b1e8456f063ba2edc65ce3c236b9 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Data/Migrations/20200324213904_Initial.Designer.cs @@ -0,0 +1,379 @@ +// <auto-generated /> +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Wasm.Authentication.Server.Data; + +namespace Wasm.Authentication.Server.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20200324213904_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.2"); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property<string>("UserCode") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<string>("ClientId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<DateTime>("CreationTime") + .HasColumnType("TEXT"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(50000); + + b.Property<string>("DeviceCode") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<DateTime?>("Expiration") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<string>("SubjectId") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property<string>("Key") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<string>("ClientId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<DateTime>("CreationTime") + .HasColumnType("TEXT"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(50000); + + b.Property<DateTime?>("Expiration") + .HasColumnType("TEXT"); + + b.Property<string>("SubjectId") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<string>("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(50); + + b.HasKey("Key"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.ToTable("PersistedGrants"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property<string>("Id") + .HasColumnType("TEXT"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<string>("NormalizedName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("ClaimType") + .HasColumnType("TEXT"); + + b.Property<string>("ClaimValue") + .HasColumnType("TEXT"); + + b.Property<string>("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("ClaimType") + .HasColumnType("TEXT"); + + b.Property<string>("ClaimValue") + .HasColumnType("TEXT"); + + b.Property<string>("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => + { + b.Property<string>("LoginProvider") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("ProviderKey") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property<string>("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => + { + b.Property<string>("UserId") + .HasColumnType("TEXT"); + + b.Property<string>("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => + { + b.Property<string>("UserId") + .HasColumnType("TEXT"); + + b.Property<string>("LoginProvider") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("Name") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Wasm.Authentication.Server.Models.ApplicationUser", b => + { + b.Property<string>("Id") + .HasColumnType("TEXT"); + + b.Property<int>("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property<string>("Email") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<bool>("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property<bool>("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property<DateTimeOffset?>("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property<string>("NormalizedEmail") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<string>("NormalizedUserName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<string>("PasswordHash") + .HasColumnType("TEXT"); + + b.Property<string>("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property<bool>("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property<string>("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property<bool>("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property<string>("UserName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Wasm.Authentication.Server.Models.UserPreference", b => + { + b.Property<string>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property<string>("ApplicationUserId") + .HasColumnType("TEXT"); + + b.Property<string>("Color") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("UserPreferences"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => + { + b.HasOne("Wasm.Authentication.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => + { + b.HasOne("Wasm.Authentication.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Wasm.Authentication.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => + { + b.HasOne("Wasm.Authentication.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Wasm.Authentication.Server.Models.UserPreference", b => + { + b.HasOne("Wasm.Authentication.Server.Models.ApplicationUser", null) + .WithOne("UserPreference") + .HasForeignKey("Wasm.Authentication.Server.Models.UserPreference", "ApplicationUserId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Data/Migrations/20200324213904_Initial.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Data/Migrations/20200324213904_Initial.cs new file mode 100644 index 0000000000000000000000000000000000000000..fcafd568d1e4bf7439376da8d155da3f10b2a1de --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Data/Migrations/20200324213904_Initial.cs @@ -0,0 +1,306 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Wasm.Authentication.Server.Data.Migrations +{ + public partial class Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column<string>(nullable: false), + Name = table.Column<string>(maxLength: 256, nullable: true), + NormalizedName = table.Column<string>(maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column<string>(nullable: false), + UserName = table.Column<string>(maxLength: 256, nullable: true), + NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true), + Email = table.Column<string>(maxLength: 256, nullable: true), + NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true), + EmailConfirmed = table.Column<bool>(nullable: false), + PasswordHash = table.Column<string>(nullable: true), + SecurityStamp = table.Column<string>(nullable: true), + ConcurrencyStamp = table.Column<string>(nullable: true), + PhoneNumber = table.Column<string>(nullable: true), + PhoneNumberConfirmed = table.Column<bool>(nullable: false), + TwoFactorEnabled = table.Column<bool>(nullable: false), + LockoutEnd = table.Column<DateTimeOffset>(nullable: true), + LockoutEnabled = table.Column<bool>(nullable: false), + AccessFailedCount = table.Column<int>(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "DeviceCodes", + columns: table => new + { + UserCode = table.Column<string>(maxLength: 200, nullable: false), + DeviceCode = table.Column<string>(maxLength: 200, nullable: false), + SubjectId = table.Column<string>(maxLength: 200, nullable: true), + ClientId = table.Column<string>(maxLength: 200, nullable: false), + CreationTime = table.Column<DateTime>(nullable: false), + Expiration = table.Column<DateTime>(nullable: false), + Data = table.Column<string>(maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DeviceCodes", x => x.UserCode); + }); + + migrationBuilder.CreateTable( + name: "PersistedGrants", + columns: table => new + { + Key = table.Column<string>(maxLength: 200, nullable: false), + Type = table.Column<string>(maxLength: 50, nullable: false), + SubjectId = table.Column<string>(maxLength: 200, nullable: true), + ClientId = table.Column<string>(maxLength: 200, nullable: false), + CreationTime = table.Column<DateTime>(nullable: false), + Expiration = table.Column<DateTime>(nullable: true), + Data = table.Column<string>(maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PersistedGrants", x => x.Key); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column<int>(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoleId = table.Column<string>(nullable: false), + ClaimType = table.Column<string>(nullable: true), + ClaimValue = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column<int>(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column<string>(nullable: false), + ClaimType = table.Column<string>(nullable: true), + ClaimValue = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column<string>(maxLength: 128, nullable: false), + ProviderKey = table.Column<string>(maxLength: 128, nullable: false), + ProviderDisplayName = table.Column<string>(nullable: true), + UserId = table.Column<string>(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column<string>(nullable: false), + RoleId = table.Column<string>(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column<string>(nullable: false), + LoginProvider = table.Column<string>(maxLength: 128, nullable: false), + Name = table.Column<string>(maxLength: 128, nullable: false), + Value = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "UserPreferences", + columns: table => new + { + Id = table.Column<string>(nullable: false), + ApplicationUserId = table.Column<string>(nullable: true), + Color = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserPreferences", x => x.Id); + table.ForeignKey( + name: "FK_UserPreferences_AspNetUsers_ApplicationUserId", + column: x => x.ApplicationUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DeviceCodes_DeviceCode", + table: "DeviceCodes", + column: "DeviceCode", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DeviceCodes_Expiration", + table: "DeviceCodes", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_Expiration", + table: "PersistedGrants", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_SubjectId_ClientId_Type", + table: "PersistedGrants", + columns: new[] { "SubjectId", "ClientId", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_UserPreferences_ApplicationUserId", + table: "UserPreferences", + column: "ApplicationUserId", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "DeviceCodes"); + + migrationBuilder.DropTable( + name: "PersistedGrants"); + + migrationBuilder.DropTable( + name: "UserPreferences"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000000000000000000000000000000000000..312d93c9a5da4e0b687e60ab2f419caf1dfb4736 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,377 @@ +// <auto-generated /> +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Wasm.Authentication.Server.Data; + +namespace Wasm.Authentication.Server.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.2"); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property<string>("UserCode") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<string>("ClientId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<DateTime>("CreationTime") + .HasColumnType("TEXT"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(50000); + + b.Property<string>("DeviceCode") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<DateTime?>("Expiration") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<string>("SubjectId") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property<string>("Key") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<string>("ClientId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<DateTime>("CreationTime") + .HasColumnType("TEXT"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(50000); + + b.Property<DateTime?>("Expiration") + .HasColumnType("TEXT"); + + b.Property<string>("SubjectId") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<string>("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(50); + + b.HasKey("Key"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.ToTable("PersistedGrants"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property<string>("Id") + .HasColumnType("TEXT"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<string>("NormalizedName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("ClaimType") + .HasColumnType("TEXT"); + + b.Property<string>("ClaimValue") + .HasColumnType("TEXT"); + + b.Property<string>("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("ClaimType") + .HasColumnType("TEXT"); + + b.Property<string>("ClaimValue") + .HasColumnType("TEXT"); + + b.Property<string>("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => + { + b.Property<string>("LoginProvider") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("ProviderKey") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property<string>("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => + { + b.Property<string>("UserId") + .HasColumnType("TEXT"); + + b.Property<string>("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => + { + b.Property<string>("UserId") + .HasColumnType("TEXT"); + + b.Property<string>("LoginProvider") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("Name") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Wasm.Authentication.Server.Models.ApplicationUser", b => + { + b.Property<string>("Id") + .HasColumnType("TEXT"); + + b.Property<int>("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property<string>("Email") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<bool>("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property<bool>("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property<DateTimeOffset?>("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property<string>("NormalizedEmail") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<string>("NormalizedUserName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<string>("PasswordHash") + .HasColumnType("TEXT"); + + b.Property<string>("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property<bool>("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property<string>("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property<bool>("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property<string>("UserName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Wasm.Authentication.Server.Models.UserPreference", b => + { + b.Property<string>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property<string>("ApplicationUserId") + .HasColumnType("TEXT"); + + b.Property<string>("Color") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("UserPreferences"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => + { + b.HasOne("Wasm.Authentication.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => + { + b.HasOne("Wasm.Authentication.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Wasm.Authentication.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => + { + b.HasOne("Wasm.Authentication.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Wasm.Authentication.Server.Models.UserPreference", b => + { + b.HasOne("Wasm.Authentication.Server.Models.ApplicationUser", null) + .WithOne("UserPreference") + .HasForeignKey("Wasm.Authentication.Server.Models.UserPreference", "ApplicationUserId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Models/ApplicationUser.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Models/ApplicationUser.cs new file mode 100644 index 0000000000000000000000000000000000000000..d770230b5343ed7f37943f141d2f8b6a7caa9bee --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Models/ApplicationUser.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Identity; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Wasm.Authentication.Server.Models +{ + public class ApplicationUser : IdentityUser + { + public UserPreference UserPreference { get; set; } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Models/UserPreference.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Models/UserPreference.cs new file mode 100644 index 0000000000000000000000000000000000000000..fb386a3272ad4d3ad90310d43041ced2d0eae618 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Models/UserPreference.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace Wasm.Authentication.Server.Models +{ + public class UserPreference + { + public string Id { get; set; } + + public string ApplicationUserId { get; set; } + + public string Color { get; set; } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Pages/Error.cshtml b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Pages/Error.cshtml new file mode 100644 index 0000000000000000000000000000000000000000..321d2852c810545aa75d9f3332c4cf54875c1699 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Pages/Error.cshtml @@ -0,0 +1,27 @@ +@page +@namespace Wasm.Authentication.Server.Pages +@model ErrorModel +@{ + ViewData["Title"] = "Error"; +} + +<h1 class="text-danger">Error.</h1> +<h2 class="text-danger">An error occurred while processing your request.</h2> + +@if (Model.ShowRequestId) +{ + <p> + <strong>Request ID:</strong> <code>@Model.RequestId</code> + </p> +} + +<h3>Development Mode</h3> +<p> + Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred. +</p> +<p> + <strong>The Development environment shouldn't be enabled for deployed applications.</strong> + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong> + and restarting the app. +</p> diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Pages/Error.cshtml.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Pages/Error.cshtml.cs new file mode 100644 index 0000000000000000000000000000000000000000..7ff93f69eb11b909c96f014f27496bab4e087222 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Pages/Error.cshtml.cs @@ -0,0 +1,27 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace Wasm.Authentication.Server +{ + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public class ErrorModel : PageModel + { + private readonly ILogger<ErrorModel> _logger; + + public ErrorModel(ILogger<ErrorModel> logger) + { + _logger = logger; + } + + public string RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + public void OnGet() + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Pages/Shared/_LoginPartial.cshtml b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Pages/Shared/_LoginPartial.cshtml new file mode 100644 index 0000000000000000000000000000000000000000..3fa8e3e7235b0afd60535f3037db2415a823bf16 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Pages/Shared/_LoginPartial.cshtml @@ -0,0 +1,38 @@ +@using Microsoft.AspNetCore.Identity +@using Wasm.Authentication.Server.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + +@inject SignInManager<ApplicationUser> SignInManager +@inject UserManager<ApplicationUser> UserManager + +@{ + string returnUrl = null; + var query = ViewContext.HttpContext.Request.Query; + if (query.ContainsKey("returnUrl")) + { + returnUrl = query["returnUrl"]; + } +} + +<ul class="navbar-nav"> + @if (SignInManager.IsSignedIn(User)) + { + <li class="nav-item"> + <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a> + </li> + <li class="nav-item"> + <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="/"> + <button type="submit" class="nav-link btn btn-link text-dark">Logout</button> + </form> + </li> + } + else + { + <li class="nav-item"> + <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register" asp-route-returnUrl="@returnUrl">Register</a> + </li> + <li class="nav-item"> + <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login" asp-route-returnUrl="@returnUrl">Login</a> + </li> + } +</ul> diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Program.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Program.cs new file mode 100644 index 0000000000000000000000000000000000000000..deee6057511812066b8b3876d3c1832b66e1d7c7 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Program.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace Wasm.Authentication.Server +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseSetting(WebHostDefaults.ApplicationKey, typeof(Program).Assembly.GetName().Name); + + // We require this line because we run in Production environment + // and static web assets are only on by default during development. + webBuilder.UseStaticWebAssets(); + webBuilder.UseStartup<Startup>(); + }); + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Startup.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Startup.cs new file mode 100644 index 0000000000000000000000000000000000000000..1224ee978fdc74c37749733c9d567b5bb8ec2636 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Startup.cs @@ -0,0 +1,78 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Wasm.Authentication.Server.Data; +using Wasm.Authentication.Server.Models; + +namespace Wasm.Authentication.Server +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + services.AddDbContext<ApplicationDbContext>(options => + options.UseSqlite(Configuration.GetConnectionString("DefaultConnection"))); + + services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) + .AddRoles<IdentityRole>() + .AddEntityFrameworkStores<ApplicationDbContext>(); + + services.AddIdentityServer() + .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => { + options.IdentityResources["openid"].UserClaims.Add("role"); + options.ApiResources.Single().UserClaims.Add("role"); + }); + + // Need to do this as it maps "role" to ClaimTypes.Role and causes issues + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role"); + + services.AddAuthentication() + .AddIdentityServerJwt(); + + services.AddMvc(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseWebAssemblyDebugging(); + } + + app.UseBlazorFrameworkFiles(); + app.UseStaticFiles(); + + app.UseRouting(); + + app.UseIdentityServer(); + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + endpoints.MapRazorPages(); + + endpoints.MapFallbackToFile("index.html"); + }); + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Wasm.Authentication.Server.csproj b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Wasm.Authentication.Server.csproj new file mode 100644 index 0000000000000000000000000000000000000000..4c09995278dfe05580762192735af3f04f0be06a --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/Wasm.Authentication.Server.csproj @@ -0,0 +1,33 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> + <!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. --> + + <!-- This project references the shared framework transitively. Prevent restore errors by setting this flag. --> + <GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks> + <CompileUsingReferenceAssemblies>false</CompileUsingReferenceAssemblies> + </PropertyGroup> + + <ItemGroup> + <!-- Remove the reference to Microsoft.AspNetCore.App from IdentityServer4 --> + <FrameworkReference Remove="Microsoft.AspNetCore.App" /> + + <Reference Include="Microsoft.AspNetCore" /> + <Reference Include="Microsoft.AspNetCore.Diagnostics" /> + <Reference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" /> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" /> + <Reference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" /> + <Reference Include="Microsoft.AspNetCore.Identity.UI" /> + <Reference Include="Microsoft.EntityFrameworkCore.Relational" /> + <Reference Include="Microsoft.EntityFrameworkCore.SQLite" /> + <Reference Include="Microsoft.Extensions.Hosting" /> + + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Wasm.Authentication.Client\Wasm.Authentication.Client.csproj" /> + <ProjectReference Include="..\Wasm.Authentication.Shared\Wasm.Authentication.Shared.csproj" /> + </ItemGroup> + +</Project> diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/appsettings.development.json b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/appsettings.development.json new file mode 100644 index 0000000000000000000000000000000000000000..8983e0fc1c5e2795ccfde0c771c6d66c88ef4a42 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/appsettings.development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/appsettings.json b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/appsettings.json new file mode 100644 index 0000000000000000000000000000000000000000..ef29c26af9ac7241682b4e5a21e6fd719f8af8da --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Server/appsettings.json @@ -0,0 +1,23 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "DataSource=app.db" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "IdentityServer": { + "Key": { + "Type": "Development" + }, + "Clients": { + "Wasm.Authentication.Client": { + "Profile": "IdentityServerSPA" + } + } + }, + "AllowedHosts": "*" +} diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Shared/Wasm.Authentication.Shared.csproj b/src/Components/WebAssembly/testassets/Wasm.Authentication.Shared/Wasm.Authentication.Shared.csproj new file mode 100644 index 0000000000000000000000000000000000000000..d849ca9036296be16aa120e8c57bf03ec0c8c5e5 --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Shared/Wasm.Authentication.Shared.csproj @@ -0,0 +1,8 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + <LangVersion>7.3</LangVersion> + </PropertyGroup> + +</Project> diff --git a/src/Components/WebAssembly/testassets/Wasm.Authentication.Shared/WeatherForecast.cs b/src/Components/WebAssembly/testassets/Wasm.Authentication.Shared/WeatherForecast.cs new file mode 100644 index 0000000000000000000000000000000000000000..04b6a67a4676a2bfd63f76825e36a2f0673d67fc --- /dev/null +++ b/src/Components/WebAssembly/testassets/Wasm.Authentication.Shared/WeatherForecast.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Wasm.Authentication.Shared +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public string Summary { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMeasurement.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMeasurement.cs index 62016cf630e6893aabe4b07521ff6af4fe14e029..91bcf385f43cfbcf3fef143e87ada293e5ff9daf 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMeasurement.cs +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkMeasurement.cs @@ -9,6 +9,6 @@ namespace Wasm.Performance.Driver { public DateTime Timestamp { get; internal set; } public string Name { get; internal set; } - public double Value { get; internal set; } + public object Value { get; internal set; } } } \ No newline at end of file diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResult.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResult.cs index 33e4c4094bdde72f310356809e94d888ca53bf87..08c3a575108c335cf6a1e6c360edba6cd02dbd95 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResult.cs +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResult.cs @@ -1,24 +1,23 @@ +using System.Collections.Generic; + namespace Wasm.Performance.Driver { class BenchmarkResult { - public string Name { get; set; } - - public BenchmarkDescriptor Descriptor { get; set; } - - public string ShortDescription { get; set; } - - public bool Success { get; set; } + /// <summary>The result of executing scenario benchmarks</summary> + public List<BenchmarkScenarioResult> ScenarioResults { get; set; } - public int NumExecutions { get; set; } + /// <summary>Downloaded application size in bytes</summary> + public long? DownloadSize { get; set; } - public double Duration { get; set; } + /// <summary>WASM memory usage</summary> + public long? WasmMemory { get; set; } - public class BenchmarkDescriptor - { - public string Name { get; set; } + // See https://developer.mozilla.org/en-US/docs/Web/API/Performance/memory + /// <summary>JS memory usage</summary> + public long? UsedJSHeapSize { get; set; } - public string Description { get; set; } - } + /// <summary>JS memory usage</summary> + public long? TotalJSHeapSize { get; set; } } -} \ No newline at end of file +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResultsStartup.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResultsStartup.cs index 7a4af028dfff0503cca66cc8577d70e5781fe3c8..4f4454dc89037a4a5fab1a47500cc2830484fba3 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResultsStartup.cs +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkResultsStartup.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Text.Json; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -25,12 +24,12 @@ namespace Wasm.Performance.Driver app.Run(async context => { - var result = await JsonSerializer.DeserializeAsync<List<BenchmarkResult>>(context.Request.Body, new JsonSerializerOptions + var result = await JsonSerializer.DeserializeAsync<BenchmarkResult>(context.Request.Body, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }); await context.Response.WriteAsync("OK"); - Program.SetBenchmarkResult(result); + Program.BenchmarkResultTask.TrySetResult(result); }); } } diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkScenarioResult.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkScenarioResult.cs new file mode 100644 index 0000000000000000000000000000000000000000..5311b1a4ade174ef5626a3ce634398edcd21a269 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/BenchmarkScenarioResult.cs @@ -0,0 +1,24 @@ +namespace Wasm.Performance.Driver +{ + class BenchmarkScenarioResult + { + public string Name { get; set; } + + public BenchmarkDescriptor Descriptor { get; set; } + + public string ShortDescription { get; set; } + + public bool Success { get; set; } + + public int NumExecutions { get; set; } + + public double Duration { get; set; } + + public class BenchmarkDescriptor + { + public string Name { get; set; } + + public string Description { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs index 8d588b64304130c8382a8f44ee8193bbeef28458..b2ae0170f67cd84eb65192584939a202836fd95b 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/Program.cs @@ -2,11 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.IO; -using System.IO.Compression; using System.Linq; +using System.Reflection; using System.Runtime.ExceptionServices; +using System.Text; using System.Text.Encodings.Web; using System.Text.Json; using System.Threading; @@ -16,119 +16,219 @@ using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using DevHostServerProgram = Microsoft.AspNetCore.Blazor.DevServer.Server.Program; +using DevHostServerProgram = Microsoft.AspNetCore.Components.WebAssembly.DevServer.Server.Program; namespace Wasm.Performance.Driver { public class Program { - static readonly TimeSpan Timeout = TimeSpan.FromMinutes(3); - static TaskCompletionSource<List<BenchmarkResult>> benchmarkResult = new TaskCompletionSource<List<BenchmarkResult>>(); + internal static TaskCompletionSource<BenchmarkResult> BenchmarkResultTask; public static async Task<int> Main(string[] args) { - var seleniumPort = 4444; + // This cancellation token manages the timeout for the stress run. + // By default the driver executes and reports a single Benchmark run. For stress runs, + // we'll pass in the duration to execute the runs in seconds. This will cause this driver + // to repeat executions for the duration specified. + var stressRunCancellation = CancellationToken.None; + var isStressRun = false; if (args.Length > 0) { - if (!int.TryParse(args[0], out seleniumPort)) + if (!int.TryParse(args[0], out var stressRunSeconds)) { - Console.Error.WriteLine("Usage Driver <selenium-port>"); + Console.Error.WriteLine("Usage Driver <stress-run-duration-seconds>"); return 1; } + + if (stressRunSeconds < 1) + { + Console.Error.WriteLine("Stress run duration must be a positive integer."); + return 1; + } + + if (stressRunSeconds > 0) + { + isStressRun = true; + + var stressRunDuration = TimeSpan.FromSeconds(stressRunSeconds); + Console.WriteLine($"Stress run duration: {stressRunDuration}."); + stressRunCancellation = new CancellationTokenSource(stressRunDuration).Token; + } } // This write is required for the benchmarking infrastructure. Console.WriteLine("Application started."); - var cancellationToken = new CancellationTokenSource(Timeout); - cancellationToken.Token.Register(() => benchmarkResult.TrySetException(new TimeoutException($"Timed out after {Timeout}"))); - - using var browser = await Selenium.CreateBrowser(seleniumPort, cancellationToken.Token); + using var browser = await Selenium.CreateBrowser(default, captureBrowserMemory: isStressRun); using var testApp = StartTestApp(); using var benchmarkReceiver = StartBenchmarkResultReceiver(); - var testAppUrl = GetListeningUrl(testApp); - var receiverUrl = GetListeningUrl(benchmarkReceiver); + if (isStressRun) + { + testAppUrl += "/stress.html"; + } + var receiverUrl = GetListeningUrl(benchmarkReceiver); Console.WriteLine($"Test app listening at {testAppUrl}."); + var firstRun = true; + var timeForEachRun = TimeSpan.FromMinutes(3); + var launchUrl = $"{testAppUrl}?resultsUrl={UrlEncoder.Default.Encode(receiverUrl)}#automated"; browser.Url = launchUrl; browser.Navigate(); - var appSize = GetBlazorAppSize(); - await Task.WhenAll(benchmarkResult.Task, appSize); - FormatAsBenchmarksOutput(benchmarkResult.Task.Result, appSize.Result); + do + { + BenchmarkResultTask = new TaskCompletionSource<BenchmarkResult>(); + using var runCancellationToken = new CancellationTokenSource(timeForEachRun); + using var registration = runCancellationToken.Token.Register(() => BenchmarkResultTask.TrySetException(new TimeoutException($"Timed out after {timeForEachRun}"))); + + var results = await BenchmarkResultTask.Task; + + FormatAsBenchmarksOutput(results, + includeMetadata: firstRun, + isStressRun: isStressRun); + + firstRun = false; + } while (isStressRun && !stressRunCancellation.IsCancellationRequested); Console.WriteLine("Done executing benchmark"); return 0; } - internal static void SetBenchmarkResult(List<BenchmarkResult> result) - { - benchmarkResult.TrySetResult(result); - } - - private static void FormatAsBenchmarksOutput(List<BenchmarkResult> results, (long publishSize, long compressedSize) sizes) + private static void FormatAsBenchmarksOutput(BenchmarkResult benchmarkResult, bool includeMetadata, bool isStressRun) { // Sample of the the format: https://github.com/aspnet/Benchmarks/blob/e55f9e0312a7dd019d1268c1a547d1863f0c7237/src/Benchmarks/Program.cs#L51-L67 var output = new BenchmarkOutput(); - foreach (var result in results) + + + if (benchmarkResult.DownloadSize != null) { - var scenarioName = result.Descriptor.Name; output.Metadata.Add(new BenchmarkMetadata { Source = "BlazorWasm", - Name = scenarioName, - ShortDescription = result.Name, - LongDescription = result.Descriptor.Description, - Format = "n2" + Name = "blazorwasm/download-size", + ShortDescription = "Download size (KB)", + LongDescription = "Download size (KB)", + Format = "n2", }); output.Measurements.Add(new BenchmarkMeasurement { Timestamp = DateTime.UtcNow, - Name = scenarioName, - Value = result.Duration, + Name = "blazorwasm/download-size", + Value = ((float)benchmarkResult.DownloadSize) / 1024, + }); + } + + if (benchmarkResult.WasmMemory != null) + { + output.Metadata.Add(new BenchmarkMetadata + { + Source = "BlazorWasm", + Name = "blazorwasm/wasm-memory", + ShortDescription = "Memory (KB)", + LongDescription = "WASM reported memory (KB)", + Format = "n2", + }); + + output.Measurements.Add(new BenchmarkMeasurement + { + Timestamp = DateTime.UtcNow, + Name = "blazorwasm/wasm-memory", + Value = ((float)benchmarkResult.WasmMemory) / 1024, + }); + + output.Metadata.Add(new BenchmarkMetadata + { + Source = "BlazorWasm", + Name = "blazorwasm/js-usedjsheapsize", + ShortDescription = "UsedJSHeapSize", + LongDescription = "JS used heap size" + }); + + output.Measurements.Add(new BenchmarkMeasurement + { + Timestamp = DateTime.UtcNow, + Name = "blazorwasm/js-usedjsheapsize", + Value = benchmarkResult.UsedJSHeapSize, + }); + + output.Metadata.Add(new BenchmarkMetadata + { + Source = "BlazorWasm", + Name = "blazorwasm/js-totaljsheapsize", + ShortDescription = "TotalJSHeapSize", + LongDescription = "JS total heap size" + }); + + output.Measurements.Add(new BenchmarkMeasurement + { + Timestamp = DateTime.UtcNow, + Name = "blazorwasm/js-totaljsheapsize", + Value = benchmarkResult.TotalJSHeapSize, }); } - // Statistics about publish sizes + // Information about the build that this was produced from output.Metadata.Add(new BenchmarkMetadata { Source = "BlazorWasm", - Name = "blazorwasm/publish-size", - ShortDescription = "Publish size (KB)", - LongDescription = "Publish size (KB)", - Format = "n2", + Name = "blazorwasm/commit", + ShortDescription = "Commit Hash", }); output.Measurements.Add(new BenchmarkMeasurement { Timestamp = DateTime.UtcNow, - Name = "blazorwasm/publish-size", - Value = sizes.publishSize / 1024, + Name = "blazorwasm/commit", + Value = typeof(Program).Assembly + .GetCustomAttributes<AssemblyMetadataAttribute>() + .FirstOrDefault(f => f.Key == "CommitHash") + ?.Value, }); - output.Metadata.Add(new BenchmarkMetadata + foreach (var result in benchmarkResult.ScenarioResults) { - Source = "BlazorWasm", - Name = "blazorwasm/compressed-publish-size", - ShortDescription = "Publish size compressed app (KB)", - LongDescription = "Publish size - compressed app (KB)", - Format = "n2", - }); + var scenarioName = result.Descriptor.Name; + output.Metadata.Add(new BenchmarkMetadata + { + Source = "BlazorWasm", + Name = scenarioName, + ShortDescription = result.Name, + LongDescription = result.Descriptor.Description, + Format = "n2" + }); - output.Measurements.Add(new BenchmarkMeasurement + output.Measurements.Add(new BenchmarkMeasurement + { + Timestamp = DateTime.UtcNow, + Name = scenarioName, + Value = result.Duration, + }); + } + + if (!includeMetadata) { - Timestamp = DateTime.UtcNow, - Name = "blazorwasm/compressed-publish-size", - Value = sizes.compressedSize / 1024, - }); + output.Metadata.Clear(); + } + + if (isStressRun) + { + output.Measurements.Add(new BenchmarkMeasurement + { + Timestamp = DateTime.UtcNow, + Name = "$$Delimiter$$", + }); + } + + var builder = new StringBuilder(); + builder.AppendLine("#StartJobStatistics"); + builder.AppendLine(JsonSerializer.Serialize(output)); + builder.AppendLine("#EndJobStatistics"); - Console.WriteLine("#StartJobStatistics"); - Console.WriteLine(JsonSerializer.Serialize(output)); - Console.WriteLine("#EndJobStatistics"); + Console.WriteLine(builder); } static IHost StartTestApp() @@ -137,6 +237,12 @@ namespace Wasm.Performance.Driver { "--urls", "http://127.0.0.1:0", "--applicationpath", typeof(TestApp.Program).Assembly.Location, +#if DEBUG + "--contentroot", + Path.GetFullPath(typeof(Program).Assembly.GetCustomAttributes<AssemblyMetadataAttribute>() + .First(f => f.Key == "TestAppLocatiion") + .Value) +#endif }; var host = DevHostServerProgram.BuildWebHost(args); @@ -178,9 +284,9 @@ namespace Wasm.Performance.Driver isDone.Set(); }); - if (!isDone.WaitOne(Timeout)) + if (!isDone.WaitOne(TimeSpan.FromSeconds(30))) { - throw new TimeoutException("Timed out waiting for: " + action); + throw new TimeoutException("Timed out waiting to start the host"); } if (edi != null) @@ -197,76 +303,5 @@ namespace Wasm.Performance.Driver .Addresses .First(); } - - static async Task<(long size, long compressedSize)> GetBlazorAppSize() - { - var testAssembly = typeof(TestApp.Program).Assembly; - var testAssemblyLocation = new FileInfo(testAssembly.Location); - var testApp = new DirectoryInfo(Path.Combine( - testAssemblyLocation.Directory.FullName, - testAssembly.GetName().Name)); - - return (GetDirectorySize(testApp), await GetBrotliCompressedSize(testApp)); - } - - static long GetDirectorySize(DirectoryInfo directory) - { - // This can happen if you run the app without publishing it. - if (!directory.Exists) - { - return 0; - } - - long size = 0; - foreach (var item in directory.EnumerateFileSystemInfos()) - { - if (item is FileInfo fileInfo) - { - size += fileInfo.Length; - } - else if (item is DirectoryInfo directoryInfo) - { - size += GetDirectorySize(directoryInfo); - } - } - - return size; - } - - static async Task<long> GetBrotliCompressedSize(DirectoryInfo directory) - { - if (!directory.Exists) - { - return 0; - } - - var tasks = new List<Task<long>>(); - foreach (var item in directory.EnumerateFileSystemInfos()) - { - if (item is FileInfo fileInfo) - { - tasks.Add(GetCompressedFileSize(fileInfo)); - } - else if (item is DirectoryInfo directoryInfo) - { - tasks.Add(GetBrotliCompressedSize(directoryInfo)); - } - } - - return (await Task.WhenAll(tasks)).Sum(s => s); - - async Task<long> GetCompressedFileSize(FileInfo fileInfo) - { - using var inputStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read, 1, useAsync: true); - using var outputStream = new MemoryStream(); - - using (var brotliStream = new BrotliStream(outputStream, CompressionLevel.Optimal, leaveOpen: true)) - { - await inputStream.CopyToAsync(brotliStream); - } - - return outputStream.Length; - } - } } } diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs b/src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs index 1c30e69e20b07b543415754e28d4593f15833f9f..da5fbe4e1afe5da478c5dcccbbb5e073eb5459b3 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/Selenium.cs @@ -1,9 +1,7 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; -using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -15,8 +13,9 @@ namespace Wasm.Performance.Driver { class Selenium { + const int SeleniumPort = 4444; static bool RunHeadlessBrowser = true; - static bool PoolForBrowserLogs = true; + static bool PoolForBrowserLogs = false; private static async ValueTask<Uri> WaitForServerAsync(int port, CancellationToken cancellationToken) { @@ -55,9 +54,9 @@ namespace Wasm.Performance.Driver throw new Exception($"Unable to connect to selenium-server at {uri}"); } - public static async Task<RemoteWebDriver> CreateBrowser(int port, CancellationToken cancellationToken) + public static async Task<RemoteWebDriver> CreateBrowser(CancellationToken cancellationToken, bool captureBrowserMemory = false) { - var uri = await WaitForServerAsync(port, cancellationToken); + var uri = await WaitForServerAsync(SeleniumPort, cancellationToken); var options = new ChromeOptions(); @@ -66,6 +65,11 @@ namespace Wasm.Performance.Driver options.AddArgument("--headless"); } + if (captureBrowserMemory) + { + options.AddArgument("--enable-precise-memory-info"); + } + options.SetLoggingPreference(LogType.Browser, LogLevel.All); var attempt = 0; diff --git a/src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj b/src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj index cf35be4e007e57346443058d983a9f86f6ec6755..6a080c57333d9e0bd02595d50e5d4e00e40417ed 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj +++ b/src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj @@ -1,8 +1,7 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> - <!-- Intentionally pinned this to .NET Core 3.1 since that's the supported version in the docker image --> - <TargetFramework>netcoreapp3.1</TargetFramework> + <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> <UseLatestAspNetCoreReference>true</UseLatestAspNetCoreReference> <OutputType>exe</OutputType> @@ -14,10 +13,18 @@ <ItemGroup> <Reference Include="Selenium.Support" /> <Reference Include="Selenium.WebDriver" /> - <ProjectReference Include="..\..\..\Blazor\DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj" /> + <ProjectReference Include="..\..\..\WebAssembly\DevServer\src\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj" /> <ProjectReference Include="..\TestApp\Wasm.Performance.TestApp.csproj" /> - - <Content Include="appsettings.json" CopyToPublishDirectory="PreserveNewest" /> </ItemGroup> + <Target Name="_AddTestProjectMetadataAttributes" BeforeTargets="BeforeCompile"> + <ItemGroup> + <AssemblyAttribute + Include="System.Reflection.AssemblyMetadataAttribute"> + <_Parameter1>TestAppLocatiion</_Parameter1> + <_Parameter2>$(MSBuildThisFileDirectory)..\TestApp\</_Parameter2> + </AssemblyAttribute> + </ItemGroup> + </Target> + </Project> diff --git a/src/Components/benchmarkapps/Wasm.Performance/README.md b/src/Components/benchmarkapps/Wasm.Performance/README.md index 9522ecc50248cd14664bee69828dadba51b30908..6110956bfc7bddcba77214840907abdb7fbc42ab 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/README.md +++ b/src/Components/benchmarkapps/Wasm.Performance/README.md @@ -18,3 +18,9 @@ To run the benchmark app in the Benchmark server, run ``` dotnet run -- --config aspnetcore/src/Components/benchmarkapps/Wasm.Performance/benchmarks.compose.json application.endpoints <BenchmarkServerUri> --scenario blazorwasmbenchmark ``` + +If you have local changes that you'd like to benchmark, the easiest way is to push your local changes and tell the server to use your branch: + +``` +dotnet run -- --config aspnetcore/src/Components/benchmarkapps/Wasm.Performance/benchmarks.compose.json application.endpoints <BenchmarkServerUri> --scenario blazorwasmbenchmark --application.buildArguments "gitBranch=mylocalchanges" +``` \ No newline at end of file diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Index.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Index.razor index e740dc121c7aec16717a7ef632ad87e2b4aef770..9de3b230e48137e0c8a00e84c0158a59f6d748d2 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Index.razor +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Index.razor @@ -6,6 +6,6 @@ Hello, world! @code { protected override void OnAfterRender(bool firstRender) { - BenchmarkEvent.Send(JSRuntime, "Rendered index.cshtml"); + BenchmarkEvent.Send(JSRuntime, "Rendered Index.razor"); } } diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Json.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Json.razor index 84722da2f17d843e72b5fce8c1bfd3fb0ce3416e..1c0d37ea9d95a08463eed9eba4d998ac4438b99b 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Json.razor +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/Json.razor @@ -24,9 +24,8 @@ } @code { - static string[] Clearances = new[] { "Alpha", "Beta", "Gamma", "Delta", "Epsilon" }; - Person smallOrgChart = GenerateOrgChart(1, 4); - Person largeOrgChart = GenerateOrgChart(5, 4); + Person smallOrgChart = Person.GenerateOrgChart(1, 4); + Person largeOrgChart = Person.GenerateOrgChart(5, 4); string smallOrgChartJson; string largeOrgChartJson; int numPeopleDeserialized; @@ -62,23 +61,6 @@ void DeserializeLarge() => numPeopleDeserialized = Deserialize(largeOrgChartJson); - static Person GenerateOrgChart(int totalDepth, int numDescendantsPerNode, int thisDepth = 0, string namePrefix = null, int siblingIndex = 0) - { - var name = $"{namePrefix ?? "CEO"} - Subordinate {siblingIndex}"; - var rng = new Random(0); - return new Person - { - Name = name, - IsAdmin = siblingIndex % 2 == 0, - Salary = 10000000 / (thisDepth + 1), - SecurityClearances = Clearances - .ToDictionary(c => c, _ => (object)(rng.Next(0, 2) == 0)), - Subordinates = Enumerable.Range(0, thisDepth < totalDepth ? numDescendantsPerNode : 0) - .Select(index => GenerateOrgChart(totalDepth, numDescendantsPerNode, thisDepth + 1, name, index)) - .ToList() - }; - } - static int Deserialize(string json) { var ceo = JsonSerializer.Deserialize<Person>(json); @@ -87,13 +69,4 @@ static int CountPeople(Person root) => 1 + (root.Subordinates?.Sum(CountPeople) ?? 0); - - class Person - { - public string Name { get; set; } - public int Salary { get; set; } - public bool IsAdmin { get; set; } - public List<Person> Subordinates { get; set; } - public Dictionary<string, object> SecurityClearances { get; set; } - } } diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/OrgChart.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/OrgChart.razor new file mode 100644 index 0000000000000000000000000000000000000000..c89854981aacbadd8322e287173d80a3ccc3ad56 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/OrgChart.razor @@ -0,0 +1,38 @@ +@page "/orgchart" +@inject IJSRuntime JSRuntime + +<h1>Org Chart</h1> +<fieldset> + <label>Depth: <input id="depth" type="number" @bind="depth" /></label> + <label>Subordinates: <input id="subs" type="number" @bind="subs" /></label> + + <button id="show" @onclick="Show">Show</button> + <button id="hide" @onclick="Hide">Hide</button> +</fieldset> + +@if (show) +{ + <PersonDisplay Person="Person.GenerateOrgChart(depth, subs)" /> +} + +@code +{ + int depth = 2; + int subs = 5; + bool show; + + protected override void OnAfterRender(bool firstRender) + { + BenchmarkEvent.Send(JSRuntime, "Finished OrgChart rendering"); + } + + void Hide() + { + show = false; + } + + void Show() + { + show = true; + } +} \ No newline at end of file diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/PersonDisplay.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/PersonDisplay.razor new file mode 100644 index 0000000000000000000000000000000000000000..00e8cdddb37c589e95509a97ecaa1c2906e04332 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/PersonDisplay.razor @@ -0,0 +1,52 @@ +@inject IJSRuntime JSRuntime + +<div class="person"> + <h2> + @Person.Name + @if (Person.IsAdmin) + { + <span>[Administrator]</span> + } + </h2> + + Salary: $<h3 class="salary">@Person.Salary</h3> + + <EditForm Model="Person"> + <div> + <label>Salary</label> + <InputNumber @bind-Value="Person.Salary" /> + </div> + + <div> + <label>Adminstrator: </label> + <InputCheckbox @bind-Value="Person.IsAdmin" /> + </div> + </EditForm> + + <ul> + @foreach (var kvp in Person.SecurityClearances) + { + <li>@kvp.Key: @kvp.Value</li> + } + </ul> +</div> + + +@foreach (var person in Person.Subordinates) +{ + <ul> + <li> + <PersonDisplay Person="person" /> + </li> + </ul> +} + +@code +{ + [Parameter] public Person Person { get; set; } + + protected override void OnAfterRender(bool firstRender) + { + BenchmarkEvent.Send(JSRuntime, "Finished PersonDisplay rendering"); + } +} \ No newline at end of file diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/TimerComponent.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/TimerComponent.razor new file mode 100644 index 0000000000000000000000000000000000000000..10def1b4e1a064ea6e60ffaf63aa7402c4dd3e16 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Pages/TimerComponent.razor @@ -0,0 +1,34 @@ +@page "/timer" +@inject IJSRuntime JSRuntime +@using System.Threading +@implements IDisposable + +<h1 style="background-color: rgb(@red, @green, @blue)">Timer component</h1> + +@code +{ + Random random = new Random(); + Timer timer; + int red = 128; + int green = 128; + int blue = 128; + + protected override void OnInitialized() + { + timer = new Timer(UpdateColor, null, 0, 100); + } + + void UpdateColor(object state) + { + InvokeAsync(() => + { + red = random.Next(0, 256); + green = random.Next(0, 256); + blue = random.Next(0, 256); + StateHasChanged(); + BenchmarkEvent.Send(JSRuntime, "Finished updating color"); + }); + } + + public void Dispose() => timer.Dispose(); +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Person.cs b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Person.cs new file mode 100644 index 0000000000000000000000000000000000000000..ecaa7d23ecda02ffea0524d8949883df1d72ef20 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Person.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Wasm.Performance.TestApp +{ + public class Person + { + static readonly string[] Clearances = new[] { "Alpha", "Beta", "Gamma", "Delta", "Epsilon" }; + + public string Name { get; set; } + public int Salary { get; set; } + public bool IsAdmin { get; set; } + public List<Person> Subordinates { get; set; } + public Dictionary<string, object> SecurityClearances { get; set; } + + public static Person GenerateOrgChart(int totalDepth, int numDescendantsPerNode, int thisDepth = 0, string namePrefix = null, int siblingIndex = 0) + { + + var name = $"{namePrefix ?? "CEO"} - Subordinate {siblingIndex}"; + var rng = new Random(0); + return new Person + { + Name = name, + IsAdmin = siblingIndex % 2 == 0, + Salary = 10000000 / (thisDepth + 1), + SecurityClearances = Clearances + .ToDictionary(c => c, _ => (object)(rng.Next(0, 2) == 0)), + Subordinates = Enumerable.Range(0, thisDepth < totalDepth ? numDescendantsPerNode : 0) + .Select(index => GenerateOrgChart(totalDepth, numDescendantsPerNode, thisDepth + 1, name, index)) + .ToList() + }; + } + } +} \ No newline at end of file diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Program.cs b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Program.cs index 57a3697382f3a8f5f2547022ab9d3896e7e55c2c..a8008a4ac3e9fbf1a328a36477ef13f20ccd0d3e 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Program.cs +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Program.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; -using Microsoft.AspNetCore.Blazor.Hosting; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; namespace Wasm.Performance.TestApp { diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/MainLayout.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/MainLayout.razor index 259daf45d656ac4b9eb6093fc47abb27db15f779..acf8fd2f8ac25e71474a73725bfd22ecdbd55e6a 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/MainLayout.razor +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Shared/MainLayout.razor @@ -4,9 +4,11 @@ <a href="">Home</a> | <a href="renderlist">RenderList</a> | -<a href="json">JSON</a> +<a href="json">JSON</a> | +<a href="orgchart">OrgChart</a> | +<a href="timer">Timer</a> -<hr/> +<hr /> <div> @Body diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Wasm.Performance.TestApp.csproj b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Wasm.Performance.TestApp.csproj index 3fb5a922a3fd380bcf863a7bd739e4c9cdf2a866..45467d69400944a07c7b36df5a5b41bb36f049ad 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/Wasm.Performance.TestApp.csproj +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/Wasm.Performance.TestApp.csproj @@ -2,11 +2,14 @@ <PropertyGroup> <TargetFramework>netstandard2.1</TargetFramework> - <ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally> <RazorLangVersion>3.0</RazorLangVersion> + + <HasReferenceAssembly>false</HasReferenceAssembly> + <IsProjectReferenceProvider>false</IsProjectReferenceProvider> + <ReferenceBlazorBuildLocally>true</ReferenceBlazorBuildLocally> </PropertyGroup> <ItemGroup> - <Reference Include="Microsoft.AspNetCore.Blazor" /> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly" /> </ItemGroup> </Project> diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/WasmMemory.cs b/src/Components/benchmarkapps/Wasm.Performance/TestApp/WasmMemory.cs new file mode 100644 index 0000000000000000000000000000000000000000..92579d127a22dd51279c041bb16c24844fd589c9 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/WasmMemory.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.JSInterop; + +namespace Wasm.Performance.TestApp +{ + public static class WasmMemory + { + [JSInvokable] + public static long GetTotalMemory() => GC.GetTotalMemory(forceFullCollection: true); + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/_Imports.razor b/src/Components/benchmarkapps/Wasm.Performance/TestApp/_Imports.razor index fef56339a95f0c04bc9f47121c5ee8822744050f..638e1ea0283aab9e73b6c0dcd0f0d9fd594d4934 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/_Imports.razor +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/_Imports.razor @@ -1,5 +1,6 @@ @using System.Net.Http @using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Web @using Microsoft.JSInterop @using Wasm.Performance.TestApp diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/blazorDownloadSize.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/blazorDownloadSize.js new file mode 100644 index 0000000000000000000000000000000000000000..8cbe6df1a6cf446e30dfaa7fb625a9edd008e809 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/blazorDownloadSize.js @@ -0,0 +1,17 @@ +import { BlazorApp } from './util/BlazorApp.js'; + +export async function getBlazorDownloadSize() { + // Clear caches + for (var key of await caches.keys()) { + await caches.delete(key); + } + + const app = new BlazorApp(); + try { + await app.start(); + const downloadSize = app.window.performance.getEntries().reduce((prev, next) => (next.encodedBodySize || 0) + prev, 0); + return downloadSize; + } finally { + app.dispose(); + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js index c1690cfac87d90c762d1bdf4f04a9d2749a64231..c69e1df19208cc29d8ed0c6c9f47d64cfd3e0fbb 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/index.js @@ -3,37 +3,47 @@ import { HtmlUI } from './lib/minibench/minibench.ui.js'; import './appStartup.js'; import './renderList.js'; import './jsonHandling.js'; +import './orgChart.js'; +import { getBlazorDownloadSize } from './blazorDownloadSize.js'; new HtmlUI('E2E Performance', '#display'); if (location.href.indexOf('#automated') !== -1) { - const query = new URLSearchParams(window.location.search); - const group = query.get('group'); - const resultsUrl = query.get('resultsUrl'); + (async function() { + const query = new URLSearchParams(window.location.search); + const resultsUrl = query.get('resultsUrl'); - groups.filter(g => !group || g.name === group).forEach(g => g.runAll()); + console.log('Calculating download size...'); + const downloadSize = await getBlazorDownloadSize(); + console.log('Download size: ', downloadSize); - const benchmarksResults = []; - onBenchmarkEvent(async (status, args) => { - switch (status) { + const scenarioResults = []; + groups.forEach(g => g.runAll()); + + onBenchmarkEvent(async (status, args) => { + switch (status) { case BenchmarkEvent.runStarted: - benchmarksResults.length = 0; + scenarioResults.length = 0; break; case BenchmarkEvent.benchmarkCompleted: case BenchmarkEvent.benchmarkError: console.log(`Completed benchmark ${args.name}`); - benchmarksResults.push(args); + scenarioResults.push(args); break; case BenchmarkEvent.runCompleted: - if (resultsUrl) { - await fetch(resultsUrl, { - method: 'post', - body: JSON.stringify(benchmarksResults) - }); - } - break; + if (resultsUrl) { + await fetch(resultsUrl, { + method: 'post', + body: JSON.stringify({ + downloadSize: downloadSize, + scenarioResults: scenarioResults + }) + }); + } + break; default: throw new Error(`Unknown status: ${status}`); } - }) + }); + })(); } diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandling.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandling.js index 698eb7cee5cab8f3596b98097f7048771823886c..05c8e421c09da05b4492f214abb18109a5f89d9c 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandling.js +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandling.js @@ -2,7 +2,7 @@ import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js' import { BlazorApp } from './util/BlazorApp.js'; import { receiveEvent } from './util/BenchmarkEvents.js'; import { setInputValue } from './util/DOM.js'; -import { largeJsonToDeserialize, largeObjectToSerialize } from './jsonHandlingData.js'; +import { largeJsonToDeserialize, largeObjectToSerialize, benchmarkJson } from './jsonHandlingData.js'; group('JSON handling', () => { let app; @@ -71,17 +71,3 @@ group('JSON handling', () => { } }); }); - -async function benchmarkJson(app, buttonSelector, resultSelector, expectedResult) { - const appDocument = app.window.document; - appDocument.querySelector('#reset-all').click(); - - let nextRenderCompletion = receiveEvent('Finished JSON processing'); - appDocument.querySelector(buttonSelector).click(); - await nextRenderCompletion; - - const resultElem = appDocument.querySelector(resultSelector); - if (resultElem.textContent != expectedResult.toString()) { - throw new Error(`Incorrect result: ${resultElem.textContent}`); - } -} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandlingData.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandlingData.js index f3274a0b40a50312c5de7d557d2aa41bc43c6f9d..b74ad68764ad744f9f4dbb970d901721bb8be148 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandlingData.js +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandlingData.js @@ -1,2 +1,18 @@ +import { receiveEvent } from './util/BenchmarkEvents.js'; + export const largeObjectToSerialize = { "name": "CEO - Subordinate 0", "salary": 10000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0", "salary": 5000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1", "salary": 5000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2", "salary": 5000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3", "salary": 5000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }; export const largeJsonToDeserialize = '{ "name": "CEO - Subordinate 0", "salary": 10000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0", "salary": 5000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1", "salary": 5000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2", "salary": 5000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3", "salary": 5000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 3333333, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 3333333, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 2500000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 2500000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 2000000, "isAdmin": true, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 2000000, "isAdmin": false, "subordinates": [{ "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 0", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 1", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 2", "salary": 1666666, "isAdmin": true, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }, { "name": "CEO - Subordinate 0 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3 - Subordinate 3", "salary": 1666666, "isAdmin": false, "subordinates": [], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }], "securityClearances": { "Alpha": false, "Beta": false, "Gamma": false, "Delta": false, "Epsilon": true } }'; + +export async function benchmarkJson(app, buttonSelector, resultSelector, expectedResult) { + const appDocument = app.window.document; + appDocument.querySelector('#reset-all').click(); + + let nextRenderCompletion = receiveEvent('Finished JSON processing'); + appDocument.querySelector(buttonSelector).click(); + await nextRenderCompletion; + + const resultElem = appDocument.querySelector(resultSelector); + if (resultElem.textContent !== expectedResult.toString()) { + throw new Error(`Incorrect result: ${resultElem.textContent}`); + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandlingStress.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandlingStress.js new file mode 100644 index 0000000000000000000000000000000000000000..336f0832aa6560058aff6879d476b936efa7782a --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/jsonHandlingStress.js @@ -0,0 +1,44 @@ +import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js'; +import { benchmarkJson} from './jsonHandlingData.js'; +import { BlazorStressApp } from './util/BlazorStressApp.js'; + +group('JSON handling', () => { + let app; + + setup(() => { + app = BlazorStressApp.instance; + app.navigateTo('json'); + }); + + benchmark('Serialize 1kb', () => + benchmarkJson(app, '#serialize-small', '#serialized-length', 935), { + descriptor: { + name: 'blazorwasm/jsonserialize-1kb', + description: 'Serialize JSON 1kb - Time in ms' + } + }); + + benchmark('Serialize 340kb', () => + benchmarkJson(app, '#serialize-large', '#serialized-length', 339803), { + descriptor: { + name: 'blazorwasm/jsonserialize-340kb', + description: 'Serialize JSON 340kb - Time in ms' + } + }); + + benchmark('Deserialize 1kb', () => + benchmarkJson(app, '#deserialize-small', '#deserialized-count', 5), { + descriptor: { + name: 'blazorwasm/jsondeserialize-1kb', + description: 'Deserialize JSON 1kb - Time in ms' + } + }); + + benchmark('Deserialize 340kb', () => + benchmarkJson(app, '#deserialize-large', '#deserialized-count', 1365), { + descriptor: { + name: 'blazorwasm/jsondeserialize-340kb', + description: 'Deserialize JSON 340kb - Time in ms' + } + }); +}); diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.ui.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.ui.js index 4384b7660b74d5fce7d667e8b09c90f37f1c344e..0a38dbe87f320640b3bad825ac1c589f14f9619e 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.ui.js +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/lib/minibench/minibench.ui.js @@ -175,7 +175,7 @@ class HtmlUI { true ); this.runButton.style.display = areAllIdle ? 'block' : 'none'; - this.stopButton.style.display = areAllIdle ? 'none' : 'block';; + this.stopButton.style.display = areAllIdle ? 'none' : 'block'; } get globalRunOptions() { diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/orgChart.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/orgChart.js new file mode 100644 index 0000000000000000000000000000000000000000..7a1466358c6c140d0d559898c043be9513817178 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/orgChart.js @@ -0,0 +1,37 @@ +import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js'; +import { BlazorApp } from './util/BlazorApp.js'; +import { measureOrgChart, measureOrgChartEdit } from './orgChartBenchmark.js'; + +group('Nested components', () => { + let app; + + setup(async () => { + app = new BlazorApp(); + await app.start(); + app.navigateTo('orgChart'); + }); + + teardown(() => { + app.dispose(); + }); + + benchmark('Render small nested component', () => measureOrgChart(app, 1, 4), { + descriptor: { + name: 'blazorwasm/orgchart-1-4-org', + description: 'Time to render a complex component with small nesting (ms)' + } + }); + benchmark('Render large nested component', () => measureOrgChart(app, 3, 3), { + descriptor: { + name: 'blazorwasm/orgchart-3-3-org', + description: 'Time to render a complex component with large nesting (ms)' + } + }); + benchmark('Render component with edit', () => measureOrgChartEdit(app, 3, 2), { + descriptor: { + name: 'blazorwasm/edit-orgchart-3-2', + description: 'Time to peform updates in a nested component (ms)' + } + }); +}); + diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/orgChartBenchmark.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/orgChartBenchmark.js new file mode 100644 index 0000000000000000000000000000000000000000..30d4e8735d5b8647d875dd167baa03cd7bede97b --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/orgChartBenchmark.js @@ -0,0 +1,53 @@ +import { receiveEvent } from './util/BenchmarkEvents.js'; +import { setInputValue } from './util/DOM.js'; + +export async function measureOrgChart(app, depth, subs) { + const appDocument = app.window.document; + setInputValue(appDocument.querySelector('#depth'), depth.toString()); + setInputValue(appDocument.querySelector('#subs'), subs.toString()); + + let nextRenderCompletion = receiveEvent('Finished OrgChart rendering'); + appDocument.querySelector('#hide').click(); + await nextRenderCompletion; + + if (appDocument.querySelectorAll('h2').length !== 0) { + throw new Error('Wrong number of items rendered'); + } + + nextRenderCompletion = receiveEvent('Finished OrgChart rendering'); + appDocument.querySelector('#show').click(); + await nextRenderCompletion; + + if (appDocument.querySelectorAll('h2').length < depth * subs) { + throw new Error('Wrong number of items rendered'); + } +} + +export async function measureOrgChartEdit(app, depth, subs) { + const appDocument = app.window.document; + setInputValue(appDocument.querySelector('#depth'), depth.toString()); + setInputValue(appDocument.querySelector('#subs'), subs.toString()); + + let nextRenderCompletion = receiveEvent('Finished OrgChart rendering'); + appDocument.querySelector('#show').click(); + await nextRenderCompletion; + + const elements = appDocument.querySelectorAll('.person'); + if (!elements) { + throw new Error("No person elements found."); + } + + const personElement = elements.item(elements.length / 2); + + const display = personElement.querySelector('.salary'); + const input = personElement.querySelector('input[type=number]'); + + nextRenderCompletion = receiveEvent('Finished PersonDisplay rendering'); + const updated = (Math.floor(Math.random() * 100000)).toString(); + setInputValue(input, updated); + await nextRenderCompletion; + + if (display.innerHTML != updated) { + throw new Error('Value not updated after render'); + } +} \ No newline at end of file diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/orgChartStress.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/orgChartStress.js new file mode 100644 index 0000000000000000000000000000000000000000..0cc9bac973b5f4f6cdf642408e014c30868af1c1 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/orgChartStress.js @@ -0,0 +1,26 @@ +import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js'; +import { BlazorStressApp } from './util/BlazorStressApp.js'; +import { measureOrgChart, measureOrgChartEdit } from './orgChartBenchmark.js'; + +group('Nested components', () => { + let app; + + setup(() => { + app = BlazorStressApp.instance; + app.navigateTo('orgChart'); + }); + + benchmark('Render large nested component', () => measureOrgChart(app, 3, 3), { + descriptor: { + name: 'blazorwasm/orgchart-3-3-org', + description: 'Time to render a complex component with large nesting (ms)' + } + }); + benchmark('Render component with edit', () => measureOrgChartEdit(app, 3, 2), { + descriptor: { + name: 'blazorwasm/edit-orgchart-3-2', + description: 'Time to peform updates in a nested component (ms)' + } + }); +}); + diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/renderList.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/renderList.js index 32a1bb8a7282cff9d4719714ecd4fc55933c1495..e40bf1b6c9ad1d414f542a93fad6ca6380d4c4bd 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/renderList.js +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/renderList.js @@ -1,7 +1,6 @@ import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js'; import { BlazorApp } from './util/BlazorApp.js'; -import { receiveEvent } from './util/BenchmarkEvents.js'; -import { setInputValue } from './util/DOM.js'; +import { measureRenderList } from './renderListBenchmark.js'; group('Rendering list', () => { let app; @@ -34,27 +33,4 @@ group('Rendering list', () => { description: 'Time to render 1000 item list (ms)' } }); - }); - -async function measureRenderList(app, numItems) { - const appDocument = app.window.document; - const numItemsTextbox = appDocument.querySelector('#num-items'); - setInputValue(numItemsTextbox, numItems.toString()); - - let nextRenderCompletion = receiveEvent('Finished rendering list'); - appDocument.querySelector('#hide-list').click(); - await nextRenderCompletion; - - if (appDocument.querySelectorAll('tbody tr').length !== 0) { - throw new Error('Wrong number of items rendered'); - } - - nextRenderCompletion = receiveEvent('Finished rendering list'); - appDocument.querySelector('#show-list').click(); - await nextRenderCompletion; - - if (appDocument.querySelectorAll('tbody tr').length !== numItems) { - throw new Error('Wrong number of items rendered'); - } -} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/renderListBenchmark.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/renderListBenchmark.js new file mode 100644 index 0000000000000000000000000000000000000000..e4c003bef98aa303a052abb2f7eed53ca5435af8 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/renderListBenchmark.js @@ -0,0 +1,25 @@ +import { receiveEvent } from './util/BenchmarkEvents.js'; +import { setInputValue } from './util/DOM.js'; + +export async function measureRenderList(app, numItems) { + const appDocument = app.window.document; + const numItemsTextbox = appDocument.querySelector('#num-items'); + setInputValue(numItemsTextbox, numItems.toString()); + + let nextRenderCompletion = receiveEvent('Finished rendering list'); + appDocument.querySelector('#hide-list').click(); + await nextRenderCompletion; + + if (appDocument.querySelectorAll('tbody tr').length !== 0) { + throw new Error('Wrong number of items rendered'); + } + + nextRenderCompletion = receiveEvent('Finished rendering list'); + appDocument.querySelector('#show-list').click(); + await nextRenderCompletion; + + if (appDocument.querySelectorAll('tbody tr').length !== numItems) { + throw new Error('Wrong number of items rendered'); + } +} + diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/renderListStress.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/renderListStress.js new file mode 100644 index 0000000000000000000000000000000000000000..4d9e5ba45bcd2a174b2f53d06539db0ce8b73116 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/renderListStress.js @@ -0,0 +1,31 @@ +import { group, setup, benchmark } from './lib/minibench/minibench.js'; +import { BlazorStressApp } from './util/BlazorStressApp.js'; +import { measureRenderList } from './renderListBenchmark.js'; + +group('Rendering list', () => { + let app; + + setup(() => { + app = BlazorStressApp.instance; + app.navigateTo('renderList'); + }); + + benchmark('Render 10 items', () => measureRenderList(app, 10), { + descriptor: { + name: 'blazorwasm/render-10-items', + description: 'Time to render 10 item list (ms)' + } + }); + benchmark('Render 100 items', () => measureRenderList(app, 100), { + descriptor: { + name: 'blazorwasm/render-100-items', + description: 'Time to render 100 item list (ms)' + } + }); + benchmark('Render 1000 items', () => measureRenderList(app, 1000), { + descriptor: { + name: 'blazorwasm/render-1000-items', + description: 'Time to render 1000 item list (ms)' + } + }); +}); diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/stress.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/stress.js new file mode 100644 index 0000000000000000000000000000000000000000..b68dac3c95c12bbef717dd3f35993e333d51a52f --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/stress.js @@ -0,0 +1,67 @@ +import { groups, BenchmarkEvent, onBenchmarkEvent } from './lib/minibench/minibench.js'; +import { HtmlUI } from './lib/minibench/minibench.ui.js'; +import './renderListStress.js'; +import './jsonHandlingStress.js'; +import './orgChartStress.js'; +import './timerStress.js'; + +import { BlazorStressApp } from './util/BlazorStressApp.js'; + +new HtmlUI('E2E Performance', '#display'); + +if (location.href.indexOf('#automated') !== -1) { + (async function () { + const query = new URLSearchParams(window.location.search); + const resultsUrl = query.get('resultsUrl'); + + // timeout in ms. Defaults to 2 minutes. + const timeout = query.get('timeout') || 2 * 60 * 1000; + const scenarioResults = []; + + await BlazorStressApp.createAsync(); + + let shouldRun = true; + setTimeout(() => shouldRun = false, timeout); + + while (shouldRun) { + const promise = new Promise((resolve, reject) => { + onBenchmarkEvent(async (status, args) => { + switch (status) { + case BenchmarkEvent.runStarted: + scenarioResults.length = 0; + break; + case BenchmarkEvent.benchmarkCompleted: + case BenchmarkEvent.benchmarkError: + console.log(`Completed benchmark ${args.name}`); + scenarioResults.push(args); + break; + case BenchmarkEvent.runCompleted: + { + const wasmMemory = BlazorStressApp.instance.window.DotNet.invokeMethod('Wasm.Performance.TestApp', 'GetTotalMemory'); + + const jsMemory = window.performance.memory; + + if (resultsUrl) { + await fetch(resultsUrl, { + method: 'post', + body: JSON.stringify({ + wasmMemory: wasmMemory, + usedJSHeapSize: jsMemory.usedJSHeapSize, + totalJSHeapSize: jsMemory.totalJSHeapSize, + scenarioResults: scenarioResults + }) + }); + } + resolve(); + break; + } + default: + reject(new Error(`Unknown status: ${status}`)); + } + }); + }); + groups.forEach(g => g.runAll()); + await promise; + } + })(); +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/timerStress.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/timerStress.js new file mode 100644 index 0000000000000000000000000000000000000000..cf4e7a95359aff1e3dd67f6c7605f753f1a45dd3 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/timerStress.js @@ -0,0 +1,32 @@ +import { group, benchmark, setup, teardown } from './lib/minibench/minibench.js'; +import { BlazorApp } from './util/BlazorApp.js'; +import { receiveEvent } from './util/BenchmarkEvents.js'; + +group('Navigation', () => { + let app; + + setup(async () => { + app = new BlazorApp(); + await app.start(); + }); + + teardown(() => app.dispose()); + + // Timers tend to make for good stress scenarios in helping identify memory leaks / use-after-dispose etc. + // While benchmarking it isn't super useful, we'll use it to keep with the theme. + benchmark('Timer', () => + benchmarkNavigation(app), { + descriptor: { + name: 'blazorwasm/timer', + description: 'Timers - Time in ms' + } + }); +}); + +async function benchmarkNavigation(app) { + for (let i = 0; i < 3; i++) { + const nextCompletion = receiveEvent('Finished updating color'); + app.navigateTo('timer'); + await nextCompletion; + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BlazorApp.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BlazorApp.js index d3211c50dbfc030a9e314697c4d234cb5cfe13fd..e4d798115ffe04824bf0222ce7dd71f429b10355 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BlazorApp.js +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BlazorApp.js @@ -12,7 +12,7 @@ export class BlazorApp { async start() { this._frame.src = 'blazor-frame.html'; - await receiveEvent('Rendered index.cshtml'); + await receiveEvent('Rendered Index.razor'); } navigateTo(url) { diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BlazorStressApp.js b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BlazorStressApp.js new file mode 100644 index 0000000000000000000000000000000000000000..46fe6885dc4337f1f09261b0f5f21ff194945457 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/benchmarks/util/BlazorStressApp.js @@ -0,0 +1,16 @@ +import { BlazorApp } from "./BlazorApp.js"; + +export class BlazorStressApp { + /** @returns {BlazorApp} */ + static get instance() { + return BlazorStressApp._instance; + } + + /** @returns {Promise<void>} */ + static createAsync() { + const instance = new BlazorApp(); + BlazorStressApp._instance = instance; + + return instance.start(); + } +} diff --git a/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/stress.html b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/stress.html new file mode 100644 index 0000000000000000000000000000000000000000..f96118e341b4b60a109cd1190918368f587bcd75 --- /dev/null +++ b/src/Components/benchmarkapps/Wasm.Performance/TestApp/wwwroot/stress.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8" /> + <title>E2EPerformance</title> + <link href="benchmarks/lib/bootstrap.min.css" rel="stylesheet" /> + <link href="benchmarks/lib/minibench/style.css" rel="stylesheet" /> +</head> +<body style="overflow: scroll;"> + <div class="container" id="display"></div> + <p class="container px-3"> + <a href="blazor-frame.html">View benchmark app ⮕</a> + </p> + + <script type="module" src="benchmarks/stress.js"></script> +</body> +</html> diff --git a/src/Components/benchmarkapps/Wasm.Performance/dockerfile b/src/Components/benchmarkapps/Wasm.Performance/dockerfile index 69f27a9212314354b7443a6d9d9248e9971e0a21..843a0eed216bff25fe60438f627a21a70b61f724 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/dockerfile +++ b/src/Components/benchmarkapps/Wasm.Performance/dockerfile @@ -1,5 +1,6 @@ FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build +ENV StressRunDuration=0 ARG DEBIAN_FRONTEND=noninteractive # Setup for nodejs @@ -21,7 +22,8 @@ RUN git init \ && git reset --hard FETCH_HEAD \ && git submodule update --init -RUN dotnet publish -c Release -r linux-x64 -o /app ./src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj +RUN ./restore.sh +RUN .dotnet/dotnet publish -c Release -r linux-x64 -o /app ./src/Components/benchmarkapps/Wasm.Performance/Driver/Wasm.Performance.Driver.csproj RUN chmod +x /app/Wasm.Performance.Driver WORKDIR /app diff --git a/src/Components/benchmarkapps/Wasm.Performance/exec.sh b/src/Components/benchmarkapps/Wasm.Performance/exec.sh index bae38ae1e166799819ae4653c37c1c242afa9b46..be913a857692cb3747316c94da9bdfab35f92e28 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/exec.sh +++ b/src/Components/benchmarkapps/Wasm.Performance/exec.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash /opt/bin/start-selenium-standalone.sh& -./Wasm.Performance.Driver +./Wasm.Performance.Driver $StressRunDuration diff --git a/src/Components/benchmarkapps/Wasm.Performance/local.dockerfile b/src/Components/benchmarkapps/Wasm.Performance/local.dockerfile index 188bc5dc5a81c264af59c3df1d20cd7df20387b1..bccdc856f8be4bc536d7e8b1592831a70bc401b7 100644 --- a/src/Components/benchmarkapps/Wasm.Performance/local.dockerfile +++ b/src/Components/benchmarkapps/Wasm.Performance/local.dockerfile @@ -1,5 +1,7 @@ FROM selenium/standalone-chrome:3.141.59-mercury as final +ENV StressRunDuration=0 + WORKDIR /app COPY ./Driver/bin/Release/netcoreapp3.1/linux-x64/publish ./ COPY ./exec.sh ./ diff --git a/src/Components/startvs.cmd b/src/Components/startvs.cmd index 1eb52561227a9e9103115a1fa11b9791a47c34b3..962cc73686e3152c15c9e2ed8f837d0665f62a5b 100644 --- a/src/Components/startvs.cmd +++ b/src/Components/startvs.cmd @@ -1,3 +1,3 @@ @ECHO OFF -%~dp0..\..\startvs.cmd %~dp0Components.sln +%~dp0..\..\startvs.cmd %~dp0Blazor.sln diff --git a/src/Components/test/E2ETest/Infrastructure/ServerFixtures/DevHostServerFixture.cs b/src/Components/test/E2ETest/Infrastructure/ServerFixtures/DevHostServerFixture.cs index d09118ef3f5d8658b03fa99f05d5d08e4e669d5d..f31cc6b89c5a1ac79552437515932cddb44f78b6 100644 --- a/src/Components/test/E2ETest/Infrastructure/ServerFixtures/DevHostServerFixture.cs +++ b/src/Components/test/E2ETest/Infrastructure/ServerFixtures/DevHostServerFixture.cs @@ -2,15 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.E2ETesting; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Hosting; -using System; using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using DevHostServerProgram = Microsoft.AspNetCore.Blazor.DevServer.Server.Program; +using DevHostServerProgram = Microsoft.AspNetCore.Components.WebAssembly.DevServer.Server.Program; namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures { diff --git a/src/Components/test/E2ETest/Infrastructure/ServerFixtures/ServerFixture.cs b/src/Components/test/E2ETest/Infrastructure/ServerFixtures/ServerFixture.cs index 67c164543caf4b6767fa1b4f700921c64791ddcd..1b1ed2a702861dcff84c4237dd620441cb018047 100644 --- a/src/Components/test/E2ETest/Infrastructure/ServerFixtures/ServerFixture.cs +++ b/src/Components/test/E2ETest/Infrastructure/ServerFixtures/ServerFixture.cs @@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures m => m.Value); } - protected static string FindSampleOrTestSitePath(string projectName) + public static string FindSampleOrTestSitePath(string projectName) { var projects = _projects.Value; if (projects.TryGetValue(projectName, out var dir)) diff --git a/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj b/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj index 30eac8a9a17d386815d6df405e77f1d78f86940d..6869d34f78095366b13d7e2559e90af3794a4c49 100644 --- a/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj +++ b/src/Components/test/E2ETest/Microsoft.AspNetCore.Components.E2ETests.csproj @@ -25,6 +25,9 @@ <!-- Avoid CS1705 errors due to mix of assemblies brought in transitively. --> <CompileUsingReferenceAssemblies>false</CompileUsingReferenceAssemblies> + + <!-- This project references the shared framework transitively. Prevent restore errors by setting this flag. --> + <GenerateErrorForMissingTargetingPacks>false</GenerateErrorForMissingTargetingPacks> </PropertyGroup> <ItemGroup> @@ -41,14 +44,15 @@ <ItemGroup> <ProjectReference Include="..\..\benchmarkapps\Wasm.Performance\TestApp\Wasm.Performance.TestApp.csproj" /> - <ProjectReference Include="..\..\Blazor\testassets\HostedInAspNet.Client\HostedInAspNet.Client.csproj" /> - <ProjectReference Include="..\..\Blazor\testassets\HostedInAspNet.Server\HostedInAspNet.Server.csproj" /> - <ProjectReference Include="..\..\Blazor\testassets\MonoSanityClient\MonoSanityClient.csproj" /> - <ProjectReference Include="..\..\Blazor\testassets\MonoSanity\MonoSanity.csproj" /> - <ProjectReference Include="..\..\Blazor\testassets\StandaloneApp\StandaloneApp.csproj" /> - <ProjectReference Include="..\..\Blazor\DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj" /> + <ProjectReference Include="..\..\WebAssembly\testassets\HostedInAspNet.Client\HostedInAspNet.Client.csproj" /> + <ProjectReference Include="..\..\WebAssembly\testassets\HostedInAspNet.Server\HostedInAspNet.Server.csproj" /> + <ProjectReference Include="..\..\WebAssembly\testassets\MonoSanityClient\MonoSanityClient.csproj" /> + <ProjectReference Include="..\..\WebAssembly\testassets\MonoSanity\MonoSanity.csproj" /> + <ProjectReference Include="..\..\WebAssembly\testassets\StandaloneApp\StandaloneApp.csproj" /> + <ProjectReference Include="..\..\WebAssembly\DevServer\src\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj" /> <ProjectReference Include="..\testassets\BasicTestApp\BasicTestApp.csproj" /> <ProjectReference Include="..\testassets\TestServer\Components.TestServer.csproj" /> + <ProjectReference Include="..\..\WebAssembly\testassets\Wasm.Authentication.Server\Wasm.Authentication.Server.csproj" /> </ItemGroup> <!-- Shared testing infrastructure for running E2E tests using selenium --> diff --git a/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs b/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs index c3e0626eb6669d0309fcf9697d9e9926440d406f..e50d519abf22c766c428330aa7efade71d96ed09 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/InteropReliabilityTests.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using TestServer; using Xunit; @@ -18,6 +19,7 @@ using Xunit.Abstractions; namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests { + [Flaky("https://github.com/dotnet/aspnetcore/issues/19666", FlakyOn.All)] public class InteropReliabilityTests : IgnitorTest<ServerStartup> { public InteropReliabilityTests(BasicTestAppServerSiteFixture<ServerStartup> serverFixture, ITestOutputHelper output) diff --git a/src/Components/test/E2ETest/ServerExecutionTests/ServerGlobalizationTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/ServerGlobalizationTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..96f2aa67a2fbcc12b36b190f0340088b2698d5af --- /dev/null +++ b/src/Components/test/E2ETest/ServerExecutionTests/ServerGlobalizationTest.cs @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using BasicTestApp; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.Components.E2ETests.Tests; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using OpenQA.Selenium.Support.UI; +using TestServer; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests +{ + // For now this is limited to server-side execution because we don't have the ability to set the + // culture in client-side Blazor. + public class ServerGlobalizationTest : GlobalizationTest<BasicTestAppServerSiteFixture<InternationalizationStartup>> + { + public ServerGlobalizationTest( + BrowserFixture browserFixture, + BasicTestAppServerSiteFixture<InternationalizationStartup> serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + } + + protected override void InitializeAsyncCore() + { + Navigate(ServerPathBase); + Browser.MountTestComponent<CulturePicker>(); + Browser.Exists(By.Id("culture-selector")); + } + + protected override void SetCulture(string culture) + { + var selector = new SelectElement(Browser.FindElement(By.Id("culture-selector"))); + selector.SelectByValue(culture); + + // Click the link to return back to the test page + Browser.Exists(By.ClassName("return-from-culture-setter")).Click(); + + // That should have triggered a page load, so wait for the main test selector to come up. + Browser.MountTestComponent<GlobalizationBindCases>(); + Browser.Exists(By.Id("globalization-cases")); + + var cultureDisplay = Browser.Exists(By.Id("culture-name-display")); + Assert.Equal($"Culture is: {culture}", cultureDisplay.Text); + } + } +} diff --git a/src/Components/test/E2ETest/ServerExecutionTests/LocalizationTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/ServerLocalizationTest.cs similarity index 87% rename from src/Components/test/E2ETest/ServerExecutionTests/LocalizationTest.cs rename to src/Components/test/E2ETest/ServerExecutionTests/ServerLocalizationTest.cs index 49d31717fccf10d82950bf884f9b5f7d5368c814..98aabb2676b0c095116e30b9728a992b77fff14c 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/LocalizationTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/ServerLocalizationTest.cs @@ -13,11 +13,9 @@ using Xunit.Abstractions; namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests { - // For now this is limited to server-side execution because we don't have the ability to set the - // culture in client-side Blazor. - public class LocalizationTest : ServerTestBase<BasicTestAppServerSiteFixture<InternationalizationStartup>> + public class ServerLocalizationTest : ServerTestBase<BasicTestAppServerSiteFixture<InternationalizationStartup>> { - public LocalizationTest( + public ServerLocalizationTest( BrowserFixture browserFixture, BasicTestAppServerSiteFixture<InternationalizationStartup> serverFixture, ITestOutputHelper output) diff --git a/src/Components/test/E2ETest/Tests/BootResourceCachingTest.cs b/src/Components/test/E2ETest/Tests/BootResourceCachingTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..ff79143e5f0d40b8d32b634b8abd160c6503b629 --- /dev/null +++ b/src/Components/test/E2ETest/Tests/BootResourceCachingTest.cs @@ -0,0 +1,154 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using HostedInAspNet.Server; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using Microsoft.Extensions.DependencyInjection; +using OpenQA.Selenium; +using OpenQA.Selenium.Support.UI; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.Tests +{ + public class BootResourceCachingTest + : ServerTestBase<AspNetSiteServerFixture> + { + // The cache name is derived from the application's base href value (in this case, '/') + private const string CacheName = "blazor-resources-/"; + + public BootResourceCachingTest( + BrowserFixture browserFixture, + AspNetSiteServerFixture serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + serverFixture.BuildWebHostMethod = Program.BuildWebHost; + } + + public override Task InitializeAsync() + { + return base.InitializeAsync(Guid.NewGuid().ToString()); + } + + [Fact] + public void CachesResourcesAfterFirstLoad() + { + // On the first load, we have to fetch everything + Navigate("/"); + WaitUntilLoaded(); + var initialResourcesRequested = GetAndClearRequestedPaths(); + Assert.NotEmpty(initialResourcesRequested.Where(path => path.EndsWith("/blazor.boot.json"))); + Assert.NotEmpty(initialResourcesRequested.Where(path => path.EndsWith("/dotnet.wasm"))); + Assert.NotEmpty(initialResourcesRequested.Where(path => path.EndsWith("/dotnet.timezones.dat"))); + Assert.NotEmpty(initialResourcesRequested.Where(path => path.EndsWith(".js"))); + Assert.NotEmpty(initialResourcesRequested.Where(path => path.EndsWith(".dll"))); + + // On subsequent loads, we skip the items referenced from blazor.boot.json + // which includes .dll files and dotnet.wasm + Navigate("about:blank"); + Navigate("/"); + WaitUntilLoaded(); + var subsequentResourcesRequested = GetAndClearRequestedPaths(); + Assert.NotEmpty(initialResourcesRequested.Where(path => path.EndsWith("/blazor.boot.json"))); + Assert.Empty(subsequentResourcesRequested.Where(path => path.EndsWith("/dotnet.wasm"))); + Assert.Empty(subsequentResourcesRequested.Where(path => path.EndsWith("/dotnet.timezones.dat"))); + Assert.NotEmpty(subsequentResourcesRequested.Where(path => path.EndsWith(".js"))); + Assert.Empty(subsequentResourcesRequested.Where(path => path.EndsWith(".dll"))); + } + + [Fact] + public void IncrementallyUpdatesCache() + { + // Perform a first load to populate the cache + Navigate("/"); + WaitUntilLoaded(); + var cacheEntryUrls1 = GetCacheEntryUrls(); + var cacheEntryForMsCorLib = cacheEntryUrls1.Single(url => url.Contains("/mscorlib.dll")); + var cacheEntryForDotNetWasm = cacheEntryUrls1.Single(url => url.Contains("/dotnet.wasm")); + var cacheEntryForDotNetWasmWithChangedHash = cacheEntryForDotNetWasm.Replace(".sha256-", ".sha256-different"); + + // Remove some items we do need, and add an item we don't need + RemoveCacheEntry(cacheEntryForMsCorLib); + RemoveCacheEntry(cacheEntryForDotNetWasm); + AddCacheEntry(cacheEntryForDotNetWasmWithChangedHash, "ignored content"); + var cacheEntryUrls2 = GetCacheEntryUrls(); + Assert.DoesNotContain(cacheEntryForMsCorLib, cacheEntryUrls2); + Assert.DoesNotContain(cacheEntryForDotNetWasm, cacheEntryUrls2); + Assert.Contains(cacheEntryForDotNetWasmWithChangedHash, cacheEntryUrls2); + + // On the next load, we'll fetch only the items we need (not things already cached) + GetAndClearRequestedPaths(); + Navigate("about:blank"); + Navigate("/"); + WaitUntilLoaded(); + var subsequentResourcesRequested = GetAndClearRequestedPaths(); + Assert.Collection(subsequentResourcesRequested.Where(url => url.Contains(".dll")), + requestedDll => Assert.Contains("/mscorlib.dll", requestedDll)); + Assert.Collection(subsequentResourcesRequested.Where(url => url.Contains(".wasm")), + requestedDll => Assert.Contains("/dotnet.wasm", requestedDll)); + + // We also update the cache (add new items, remove unnecessary items) + var cacheEntryUrls3 = GetCacheEntryUrls(); + Assert.Contains(cacheEntryForMsCorLib, cacheEntryUrls3); + Assert.Contains(cacheEntryForDotNetWasm, cacheEntryUrls3); + Assert.DoesNotContain(cacheEntryForDotNetWasmWithChangedHash, cacheEntryUrls3); + } + + private IReadOnlyCollection<string> GetCacheEntryUrls() + { + var js = @" + (async function(cacheName, completedCallback) { + const cache = await caches.open(cacheName); + const keys = await cache.keys(); + const urls = keys.map(r => r.url); + completedCallback(urls); + }).apply(null, arguments)"; + var jsExecutor = (IJavaScriptExecutor)Browser; + var result = (IEnumerable<object>)jsExecutor.ExecuteAsyncScript(js, CacheName); + return result.Cast<string>().ToList(); + } + + private void RemoveCacheEntry(string url) + { + var js = @" + (async function(cacheName, urlToRemove, completedCallback) { + const cache = await caches.open(cacheName); + await cache.delete(urlToRemove); + completedCallback(); + }).apply(null, arguments)"; + ((IJavaScriptExecutor)Browser).ExecuteAsyncScript(js, CacheName, url); + } + + private void AddCacheEntry(string url, string content) + { + var js = @" + (async function(cacheName, urlToAdd, contentToAdd, completedCallback) { + const cache = await caches.open(cacheName); + await cache.put(urlToAdd, new Response(contentToAdd)); + completedCallback(); + }).apply(null, arguments)"; + ((IJavaScriptExecutor)Browser).ExecuteAsyncScript(js, CacheName, url, content); + } + + private IReadOnlyCollection<string> GetAndClearRequestedPaths() + { + var requestLog = _serverFixture.Host.Services.GetRequiredService<BootResourceRequestLog>(); + var result = requestLog.RequestPaths.ToList(); + requestLog.Clear(); + return result; + } + + private void WaitUntilLoaded() + { + new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until( + driver => driver.FindElement(By.TagName("h1")).Text == "Hello, world!"); + } + } +} diff --git a/src/Components/test/E2ETest/Tests/ClientSideHostingTest.cs b/src/Components/test/E2ETest/Tests/ClientSideHostingTest.cs index 5cc392e7dce71ccb5d6d640de309b013d9c95e96..a45e5363235e8d64b81cba519648ec283b264dc4 100644 --- a/src/Components/test/E2ETest/Tests/ClientSideHostingTest.cs +++ b/src/Components/test/E2ETest/Tests/ClientSideHostingTest.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests [Fact] public void MapFallbackToClientSideBlazor_FilePath() { - Navigate("/filepath"); + Navigate("/subdir/filepath"); WaitUntilLoaded(); Assert.NotNull(Browser.FindElement(By.Id("test-selector"))); } @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests [Fact] public void MapFallbackToClientSideBlazor_Pattern_FilePath() { - Navigate("/pattern_filepath/test"); + Navigate("/subdir/pattern_filepath/test"); WaitUntilLoaded(); Assert.NotNull(Browser.FindElement(By.Id("test-selector"))); } @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests [Fact] public void MapFallbackToClientSideBlazor_AssemblyPath_FilePath() { - Navigate("/assemblypath_filepath"); + Navigate("/subdir/assemblypath_filepath"); WaitUntilLoaded(); Assert.NotNull(Browser.FindElement(By.Id("test-selector"))); } @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests [Fact] public void MapFallbackToClientSideBlazor_AssemblyPath_Pattern_FilePath() { - Navigate("/assemblypath_pattern_filepath/test"); + Navigate("/subdir/assemblypath_pattern_filepath/test"); WaitUntilLoaded(); Assert.NotNull(Browser.FindElement(By.Id("test-selector"))); } diff --git a/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs b/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs index fa93b857177a28affebd4d54c91e1189fbe43505..fe5ce079a553cd1719daab7297bdc8f1cd0ba5b2 100644 --- a/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs +++ b/src/Components/test/E2ETest/Tests/ComponentRenderingTest.cs @@ -328,8 +328,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests var showPromptButton = appElement.FindElements(By.TagName("button")).First(); showPromptButton.Click(); - var modal = new WebDriverWait(Browser, TimeSpan.FromSeconds(3)) - .Until(SwitchToAlert); + var modal = Browser.Exists(() => Browser.SwitchTo().Alert(), TimeSpan.FromSeconds(3)); modal.SendKeys("Some value from test"); modal.Accept(); var promptResult = appElement.FindElement(By.TagName("strong")); @@ -645,17 +644,5 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests && completeLIs[0].FindElement(By.CssSelector(".item-isdone")).Selected; }); } - - static IAlert SwitchToAlert(IWebDriver driver) - { - try - { - return driver.SwitchTo().Alert(); - } - catch (NoAlertPresentException) - { - return null; - } - } } } diff --git a/src/Components/test/E2ETest/Tests/ErrorNotificationTest.cs b/src/Components/test/E2ETest/Tests/ErrorNotificationTest.cs index 883d1bc5ab21c74f7be843ae03c627aaa8b69d15..1076e188324ccf604ef1956157fce840ef46b962 100644 --- a/src/Components/test/E2ETest/Tests/ErrorNotificationTest.cs +++ b/src/Components/test/E2ETest/Tests/ErrorNotificationTest.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client); Browser.MountTestComponent<ErrorComponent>(); Browser.Exists(By.Id("blazor-error-ui")); - Browser.Exists(By.TagName("button")); + Browser.Exists(By.Id("throw-simple-exception")); } [Fact] @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests var errorUi = Browser.FindElement(By.Id("blazor-error-ui")); Assert.Equal("none", errorUi.GetCssValue("display")); - var causeErrorButton = Browser.FindElement(By.TagName("button")); + var causeErrorButton = Browser.FindElement(By.Id("throw-simple-exception")); causeErrorButton.Click(); Browser.Exists(By.CssSelector("#blazor-error-ui[style='display: block;']"), TimeSpan.FromSeconds(10)); @@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests [Fact] public void ShowsErrorNotification_OnError_Reload() { - var causeErrorButton = Browser.Exists(By.TagName("button")); + var causeErrorButton = Browser.Exists(By.Id("throw-simple-exception")); var errorUi = Browser.FindElement(By.Id("blazor-error-ui")); Assert.Equal("none", errorUi.GetCssValue("display")); diff --git a/src/Components/test/E2ETest/Tests/FormsTestWithExperimentalValidator.cs b/src/Components/test/E2ETest/Tests/FormsTestWithExperimentalValidator.cs deleted file mode 100644 index dc2fb06b33243155ff0e00ebef2076416a773315..0000000000000000000000000000000000000000 --- a/src/Components/test/E2ETest/Tests/FormsTestWithExperimentalValidator.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using BasicTestApp.FormsTest; -using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; -using Microsoft.AspNetCore.E2ETesting; -using OpenQA.Selenium; -using OpenQA.Selenium.Support.UI; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.AspNetCore.Components.E2ETest.Tests -{ - public class FormsTestWithExperimentalValidator : FormsTest - { - public FormsTestWithExperimentalValidator( - BrowserFixture browserFixture, - ToggleExecutionModeServerFixture<BasicTestApp.Program> serverFixture, - ITestOutputHelper output) : base(browserFixture, serverFixture, output) - { - } - - protected override IWebElement MountSimpleValidationComponent() - => Browser.MountTestComponent<SimpleValidationComponentUsingExperimentalValidator>(); - - protected override IWebElement MountTypicalValidationComponent() - => Browser.MountTestComponent<TypicalValidationComponentUsingExperimentalValidator>(); - - [Fact] - public void EditFormWorksWithNestedValidation() - { - var appElement = Browser.MountTestComponent<ExperimentalValidationComponent>(); - - var nameInput = appElement.FindElement(By.CssSelector(".name input")); - var emailInput = appElement.FindElement(By.CssSelector(".email input")); - var confirmEmailInput = appElement.FindElement(By.CssSelector(".confirm-email input")); - var streetInput = appElement.FindElement(By.CssSelector(".street input")); - var zipInput = appElement.FindElement(By.CssSelector(".zip input")); - var countryInput = new SelectElement(appElement.FindElement(By.CssSelector(".country select"))); - var descriptionInput = appElement.FindElement(By.CssSelector(".description input")); - var weightInput = appElement.FindElement(By.CssSelector(".weight input")); - - var submitButton = appElement.FindElement(By.CssSelector("button[type=submit]")); - - submitButton.Click(); - - Browser.Equal(4, () => appElement.FindElements(By.CssSelector(".all-errors .validation-message")).Count); - - Browser.Equal("Enter a name", () => appElement.FindElement(By.CssSelector(".name .validation-message")).Text); - Browser.Equal("Enter an email", () => appElement.FindElement(By.CssSelector(".email .validation-message")).Text); - Browser.Equal("A street address is required.", () => appElement.FindElement(By.CssSelector(".street .validation-message")).Text); - Browser.Equal("Description is required.", () => appElement.FindElement(By.CssSelector(".description .validation-message")).Text); - - // Verify class-level validation - nameInput.SendKeys("Some person"); - emailInput.SendKeys("test@example.com"); - countryInput.SelectByValue("Mordor"); - descriptionInput.SendKeys("Fragile staff"); - streetInput.SendKeys("Mount Doom\t"); - - submitButton.Click(); - - // Verify member validation from IValidatableObject on a model property, CustomValidationAttribute on a model attribute, and BlazorCompareAttribute. - Browser.Equal("A ZipCode is required", () => appElement.FindElement(By.CssSelector(".zip .validation-message")).Text); - Browser.Equal("'Confirm email address' and 'EmailAddress' do not match.", () => appElement.FindElement(By.CssSelector(".confirm-email .validation-message")).Text); - Browser.Equal("Fragile items must be placed in secure containers", () => appElement.FindElement(By.CssSelector(".item-error .validation-message")).Text); - Browser.Equal(3, () => appElement.FindElements(By.CssSelector(".all-errors .validation-message")).Count); - - zipInput.SendKeys("98052"); - confirmEmailInput.SendKeys("test@example.com"); - descriptionInput.Clear(); - weightInput.SendKeys("0"); - descriptionInput.SendKeys("The One Ring\t"); - - submitButton.Click(); - // Verify validation from IValidatableObject on the model. - Browser.Equal("Some items in your list cannot be delivered.", () => appElement.FindElement(By.CssSelector(".model-errors .validation-message")).Text); - - Browser.Single(() => appElement.FindElements(By.CssSelector(".all-errors .validation-message"))); - - // Let's make sure the form submits - descriptionInput.Clear(); - descriptionInput.SendKeys("A different ring\t"); - submitButton.Click(); - - Browser.Empty(() => appElement.FindElements(By.CssSelector(".all-errors .validation-message"))); - Browser.Equal("OnValidSubmit", () => appElement.FindElement(By.CssSelector(".submission-log")).Text); - } - } -} diff --git a/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs b/src/Components/test/E2ETest/Tests/GlobalizationTest.cs similarity index 88% rename from src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs rename to src/Components/test/E2ETest/Tests/GlobalizationTest.cs index 76a01c13f3b3da4775d93d5229d02f6ba42b72a6..0b96ee4e34d0fd80d8aa44099d7db467896f0644 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/GlobalizationTest.cs +++ b/src/Components/test/E2ETest/Tests/GlobalizationTest.cs @@ -3,36 +3,24 @@ using System; using System.Globalization; -using BasicTestApp; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.E2ETesting; using OpenQA.Selenium; -using OpenQA.Selenium.Support.UI; -using TestServer; using Xunit; using Xunit.Abstractions; -namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests +namespace Microsoft.AspNetCore.Components.E2ETests.Tests { - // For now this is limited to server-side execution because we don't have the ability to set the - // culture in client-side Blazor. - public class GlobalizationTest : ServerTestBase<BasicTestAppServerSiteFixture<InternationalizationStartup>> + public abstract class GlobalizationTest<TServerFixture> : ServerTestBase<TServerFixture> + where TServerFixture : ServerFixture { - public GlobalizationTest( - BrowserFixture browserFixture, - BasicTestAppServerSiteFixture<InternationalizationStartup> serverFixture, - ITestOutputHelper output) + public GlobalizationTest(BrowserFixture browserFixture, TServerFixture serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture, output) { } - protected override void InitializeAsyncCore() - { - Navigate(ServerPathBase); - Browser.MountTestComponent<CulturePicker>(); - Browser.Exists(By.Id("culture-selector")); - } + protected abstract void SetCulture(string culture); [Theory] [InlineData("en-US")] @@ -222,21 +210,5 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString(cultureInfo), () => display.Text); Browser.Equal(new DateTimeOffset(new DateTime(2000, 1, 2)).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), () => input.GetAttribute("value")); } - - private void SetCulture(string culture) - { - var selector = new SelectElement(Browser.FindElement(By.Id("culture-selector"))); - selector.SelectByValue(culture); - - // Click the link to return back to the test page - Browser.Exists(By.ClassName("return-from-culture-setter")).Click(); - - // That should have triggered a page load, so wait for the main test selector to come up. - Browser.MountTestComponent<GlobalizationBindCases>(); - Browser.Exists(By.Id("globalization-cases")); - - var cultureDisplay = Browser.Exists(By.Id("culture-name-display")); - Assert.Equal($"Culture is: {culture}", cultureDisplay.Text); - } } } diff --git a/src/Components/test/E2ETest/Tests/JsonSerializationTest.cs b/src/Components/test/E2ETest/Tests/JsonSerializationTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..c9d4de62ebef07067209a061348b4f9b72f1cc82 --- /dev/null +++ b/src/Components/test/E2ETest/Tests/JsonSerializationTest.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using BasicTestApp; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.Tests +{ + public class JsonSerializationTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>> + { + public JsonSerializationTest( + BrowserFixture browserFixture, + ToggleExecutionModeServerFixture<Program> serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + } + + protected override void InitializeAsyncCore() + { + Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client); + Browser.MountTestComponent<JsonSerializationCases>(); + Browser.Exists(By.Id("json-serialization-cases")); + } + + [Fact] + public void JsonSerializationCasesWork() + { + Browser.Equal("Lord Smythe", () => Browser.FindElement(By.Id("deserialized-name")).Text); + Browser.Equal("68", () => Browser.FindElement(By.Id("deserialized-age")).Text); + Browser.Equal("Vexed", () => Browser.FindElement(By.Id("deserialized-mood")).Text); + } + } +} diff --git a/src/Components/test/E2ETest/Tests/MonoSanityTest.cs b/src/Components/test/E2ETest/Tests/MonoSanityTest.cs index b8db27fd2b211a43ebb4695ce6d0414bdc1345bb..181a1f99741d956a32441216d4048760d4323935 100644 --- a/src/Components/test/E2ETest/Tests/MonoSanityTest.cs +++ b/src/Components/test/E2ETest/Tests/MonoSanityTest.cs @@ -137,7 +137,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests { Browser.FindElement(By.CssSelector("#getRuntimeInformation button")).Click(); Assert.Equal( - "OSDescription: 'web'; OSArchitecture: 'X86'; IsOSPlatform(WEBASSEMBLY): 'True'", + "OSDescription: 'web'; OSArchitecture: 'X86'; IsOSPlatform(BROWSER): 'True'", GetValue(Browser, "getRuntimeInformationResult")); } diff --git a/src/Components/test/E2ETest/Tests/WebAssemblyAuthenticationTests.cs b/src/Components/test/E2ETest/Tests/WebAssemblyAuthenticationTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..8fce8f95138cdf71705b077693ecda095cdb9206 --- /dev/null +++ b/src/Components/test/E2ETest/Tests/WebAssemblyAuthenticationTests.cs @@ -0,0 +1,487 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using OpenQA.Selenium; +using OpenQA.Selenium.Support.UI; +using Wasm.Authentication.Server; +using Wasm.Authentication.Server.Data; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.Tests +{ + public class WebAssemblyAuthenticationTests : ServerTestBase<AspNetSiteServerFixture> + { + private static readonly SqliteConnection _connection; + + // We create a conection here and open it as the in memory Db will delete the database + // as soon as there are no open connections to it. + static WebAssemblyAuthenticationTests() + { + _connection = new SqliteConnection($"DataSource=:memory:"); + _connection.Open(); + } + + public WebAssemblyAuthenticationTests( + BrowserFixture browserFixture, + AspNetSiteServerFixture serverFixture, + ITestOutputHelper output) : + base(browserFixture, serverFixture, output) + { + _serverFixture.ApplicationAssembly = typeof(Program).Assembly; + + _serverFixture.AdditionalArguments.Clear(); + + _serverFixture.BuildWebHostMethod = args => Program.CreateHostBuilder(args) + .ConfigureServices(services => SetupTestDatabase<ApplicationDbContext>(services, _connection)) + .Build(); + } + + public override Task InitializeAsync() => base.InitializeAsync(Guid.NewGuid().ToString()); + + protected override void InitializeAsyncCore() + { + Navigate("/", noReload: true); + EnsureDatabaseCreated(_serverFixture.Host.Services); + WaitUntilLoaded(); + } + + [Fact] + public void WasmAuthentication_Loads() + { + Assert.Equal("Wasm.Authentication.Client", Browser.Title); + } + + [Fact] + public void AnonymousUser_GetsRedirectedToLogin_AndBackToOriginalProtectedResource() + { + var link = By.PartialLinkText("Fetch data"); + var page = "/Identity/Account/Login"; + + ClickAndNavigate(link, page); + + var userName = $"{Guid.NewGuid()}@example.com"; + var password = $"!Test.Password1$"; + + FirstTimeRegister(userName, password); + + ValidateFetchData(); + } + + [Fact] + public void CanPreserveApplicationState_DuringLogIn() + { + var originalAppState = Browser.Exists(By.Id("app-state")).Text; + + var link = By.PartialLinkText("Fetch data"); + var page = "/Identity/Account/Login"; + + ClickAndNavigate(link, page); + + var userName = $"{Guid.NewGuid()}@example.com"; + var password = $"!Test.Password1$"; + + FirstTimeRegister(userName, password); + + ValidateFetchData(); + + var homeLink = By.PartialLinkText("Home"); + var homePage = "/"; + ClickAndNavigate(homeLink, homePage); + + var restoredAppState = Browser.Exists(By.Id("app-state")).Text; + Assert.Equal(originalAppState, restoredAppState); + } + + [Fact] + public void CanShareUserRolesBetweenClientAndServer() + { + ClickAndNavigate(By.PartialLinkText("Log in"), "/Identity/Account/Login"); + + var userName = $"{Guid.NewGuid()}@example.com"; + var password = $"!Test.Password1$"; + FirstTimeRegister(userName, password); + + ClickAndNavigate(By.PartialLinkText("Make admin"), "/new-admin"); + + ClickAndNavigate(By.PartialLinkText("Settings"), "/admin-settings"); + + Browser.Exists(By.Id("admin-action")).Click(); + + Browser.Exists(By.Id("admin-success")); + } + + + private void ClickAndNavigate(By link, string page) + { + Browser.FindElement(link).Click(); + Browser.Contains(page, () => Browser.Url); + } + + [Fact] + public void AnonymousUser_CanRegister_AndGetLoggedIn() + { + ClickAndNavigate(By.PartialLinkText("Register"), "/Identity/Account/Register"); + + var userName = $"{Guid.NewGuid()}@example.com"; + var password = $"!Test.Password1$"; + RegisterCore(userName, password); + CompleteProfileDetails(); + + // Need to navigate to fetch page + Browser.FindElement(By.PartialLinkText("Fetch data")).Click(); + + // Can navigate to the 'fetch data' page + ValidateFetchData(); + } + + [Fact] + public void AuthenticatedUser_ProfileIncludesDetails_And_AccessToken() + { + ClickAndNavigate(By.PartialLinkText("User"), "/Identity/Account/Login"); + + var userName = $"{Guid.NewGuid()}@example.com"; + var password = $"!Test.Password1$"; + FirstTimeRegister(userName, password); + + Browser.Contains("user", () => Browser.Url); + Browser.Equal($"Welcome {userName}", () => Browser.FindElement(By.TagName("h1")).Text); + + var claims = Browser.FindElements(By.CssSelector("p.claim")) + .Select(e => + { + var pair = e.Text.Split(":"); + return (pair[0].Trim(), pair[1].Trim()); + }) + .Where(c => !new[] { "s_hash", "auth_time", "sid", "sub" }.Contains(c.Item1)) + .OrderBy(o => o.Item1) + .ToArray(); + + Assert.Equal(5, claims.Length); + + Assert.Equal(new[] + { + ("amr", "pwd"), + ("idp", "local"), + ("name", userName), + ("NewUser", "true"), + ("preferred_username", userName) + }, + claims); + + var token = Browser.Exists(By.Id("access-token")).Text; + Assert.NotNull(token); + var payload = JsonSerializer.Deserialize<JwtPayload>(Base64UrlTextEncoder.Decode(token.Split(".")[1])); + + Assert.StartsWith("http://127.0.0.1", payload.Issuer); + Assert.StartsWith("Wasm.Authentication.ServerAPI", payload.Audience); + Assert.StartsWith("Wasm.Authentication.Client", payload.ClientId); + Assert.Equal(new[] + { + "openid", + "profile", + "Wasm.Authentication.ServerAPI" + }, + payload.Scopes); + + var currentTime = DateTimeOffset.Parse(Browser.Exists(By.Id("current-time")).Text); + var tokenExpiration = DateTimeOffset.Parse(Browser.Exists(By.Id("access-token-expires")).Text); + Assert.True(currentTime.AddMinutes(50) < tokenExpiration); + Assert.True(currentTime.AddMinutes(60) >= tokenExpiration); + } + + [Fact] + public void AuthenticatedUser_CanGoToProfile() + { + ClickAndNavigate(By.PartialLinkText("Register"), "/Identity/Account/Register"); + + var userName = $"{Guid.NewGuid()}@example.com"; + var password = $"!Test.Password1$"; + RegisterCore(userName, password); + CompleteProfileDetails(); + + ClickAndNavigate(By.PartialLinkText($"Hello, {userName}!"), "/Identity/Account/Manage"); + + Browser.Navigate().Back(); + Browser.Equal("/", () => new Uri(Browser.Url).PathAndQuery); + } + + [Fact] + public void RegisterAndBack_DoesNotCause_RedirectLoop() + { + Browser.FindElement(By.PartialLinkText("Register")).Click(); + + // We will be redirected to the identity UI + Browser.Contains("/Identity/Account/Register", () => Browser.Url); + + Browser.Navigate().Back(); + + Browser.Equal("/", () => new Uri(Browser.Url).PathAndQuery); + } + + [Fact] + public void LoginAndBack_DoesNotCause_RedirectLoop() + { + Browser.FindElement(By.PartialLinkText("Log in")).Click(); + + // We will be redirected to the identity UI + Browser.Contains("/Identity/Account/Login", () => Browser.Url); + + Browser.Navigate().Back(); + + Browser.Equal("/", () => new Uri(Browser.Url).PathAndQuery); + } + + [Fact] + public void NewlyRegisteredUser_CanLogOut() + { + ClickAndNavigate(By.PartialLinkText("Register"), "/Identity/Account/Register"); + + var userName = $"{Guid.NewGuid()}@example.com"; + var password = $"!Test.Password1$"; + RegisterCore(userName, password); + CompleteProfileDetails(); + + ValidateLogout(); + } + + [Fact] + public void AlreadyRegisteredUser_CanLogOut() + { + ClickAndNavigate(By.PartialLinkText("Register"), "/Identity/Account/Register"); + + var userName = $"{Guid.NewGuid()}@example.com"; + var password = $"!Test.Password1$"; + RegisterCore(userName, password); + CompleteProfileDetails(); + + ValidateLogout(); + + Browser.Navigate().GoToUrl("data:"); + Navigate("/"); + WaitUntilLoaded(); + + ClickAndNavigate(By.PartialLinkText("Log in"), "/Identity/Account/Login"); + + // Now we can login + LoginCore(userName, password); + + ValidateLoggedIn(userName); + + ValidateLogout(); + } + + [Fact] + public void LoggedInUser_OnTheIdP_CanLogInSilently() + { + ClickAndNavigate(By.PartialLinkText("Register"), "/Identity/Account/Register"); + + var userName = $"{Guid.NewGuid()}@example.com"; + var password = $"!Test.Password1$"; + RegisterCore(userName, password); + CompleteProfileDetails(); + ValidateLoggedIn(userName); + + // Clear the existing storage on the page and refresh + Browser.Exists(By.Id("test-clear-storage")).Click(); + Browser.Exists(By.Id("test-refresh-page")).Click(); + + ValidateLoggedIn(userName); + } + + [Fact] + public void CanNotRedirect_To_External_ReturnUrl() + { + Browser.Navigate().GoToUrl(new Uri(new Uri(Browser.Url), "/authentication/login?returnUrl=https%3A%2F%2Fwww.bing.com").AbsoluteUri); + WaitUntilLoaded(skipHeader: true); + Browser.Exists(By.CssSelector("[style=\"display: block;\"]")); + Assert.NotEmpty(Browser.GetBrowserLogs(LogLevel.Severe)); + } + + [Fact] + public async Task CanNotTrigger_Logout_WithNavigation() + { + Browser.Navigate().GoToUrl(new Uri(new Uri(Browser.Url), "/authentication/logout").AbsoluteUri); + WaitUntilLoaded(skipHeader: true); + Browser.Contains("/authentication/logout-failed", () => Browser.Url); + await Task.Delay(3000); + Browser.Contains("/authentication/logout-failed", () => Browser.Url); + } + + private void ValidateLoggedIn(string userName) + { + Browser.Exists(By.CssSelector("button.nav-link.btn.btn-link")); + Browser.Exists(By.PartialLinkText($"Hello, {userName}!")); + } + + private void LoginCore(string userName, string password) + { + Browser.FindElement(By.PartialLinkText("Login")).Click(); + Browser.Exists(By.Name("Input.Email")); + Browser.FindElement(By.Name("Input.Email")).SendKeys(userName); + Browser.FindElement(By.Name("Input.Password")).SendKeys(password); + Browser.FindElement(By.Id("login-submit")).Click(); + } + + private void ValidateLogout() + { + Browser.Exists(By.CssSelector("button.nav-link.btn.btn-link")); + + // Click logout button + Browser.FindElement(By.CssSelector("button.nav-link.btn.btn-link")).Click(); + + Browser.Contains("/authentication/logged-out", () => Browser.Url); + Browser.True(() => Browser.FindElements(By.TagName("p")).Any(e => e.Text == "You are logged out.")); + } + + private void ValidateFetchData() + { + // Can navigate to the 'fetch data' page + Browser.Contains("fetchdata", () => Browser.Url); + Browser.Equal("Weather forecast", () => Browser.FindElement(By.TagName("h1")).Text); + + // Asynchronously loads and displays the table of weather forecasts + Browser.Exists(By.CssSelector("table>tbody>tr")); + Browser.Equal(5, () => Browser.FindElements(By.CssSelector("p+table>tbody>tr")).Count); + } + + private void FirstTimeRegister(string userName, string password) + { + Browser.FindElement(By.PartialLinkText("Register as a new user")).Click(); + RegisterCore(userName, password); + CompleteProfileDetails(); + } + + private void CompleteProfileDetails() + { + Browser.Exists(By.PartialLinkText("Home")); + Browser.Contains("/preferences", () => Browser.Url); + Browser.FindElement(By.Id("color-preference")).SendKeys("Red"); + Browser.FindElement(By.Id("submit-preference")).Click(); + } + + private void RegisterCore(string userName, string password) + { + Browser.Exists(By.Name("Input.Email")); + Browser.FindElement(By.Name("Input.Email")).SendKeys(userName); + Browser.FindElement(By.Name("Input.Password")).SendKeys(password); + Browser.FindElement(By.Name("Input.ConfirmPassword")).SendKeys(password); + Browser.FindElement(By.Id("registerSubmit")).Click(); + + // We will be redirected to the RegisterConfirmation + Browser.Contains("/Identity/Account/RegisterConfirmation", () => Browser.Url); + try + { + // For some reason the test sometimes get stuck here. Given that this is not something we are testing, to avoid + // this we'll retry once to minify the chances it happens on CI runs. + ClickAndNavigate(By.PartialLinkText("Click here to confirm your account"), "/Identity/Account/ConfirmEmail"); + } + catch + { + ClickAndNavigate(By.PartialLinkText("Click here to confirm your account"), "/Identity/Account/ConfirmEmail"); + } + + // Now we can login + Browser.FindElement(By.PartialLinkText("Login")).Click(); + Browser.Exists(By.Name("Input.Email")); + Browser.FindElement(By.Name("Input.Email")).SendKeys(userName); + Browser.FindElement(By.Name("Input.Password")).SendKeys(password); + Browser.FindElement(By.Id("login-submit")).Click(); + } + + private void WaitUntilLoaded(bool skipHeader = false) + { + new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until( + driver => driver.FindElement(By.TagName("app")).Text != "Loading..."); + + if (!skipHeader) + { + // All pages in the text contain an h1 element. This helps us wait until the router has intercepted links as that + // happens before rendering the underlying page. + Browser.Exists(By.TagName("h1")); + } + } + + public static IServiceCollection SetupTestDatabase<TContext>(IServiceCollection services, DbConnection connection) where TContext : DbContext + { + var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<TContext>)); + if (descriptor != null) + { + services.Remove(descriptor); + } + + services.AddScoped(p => + DbContextOptionsFactory<TContext>( + p, + (sp, options) => options + .ConfigureWarnings(b => b.Log(CoreEventId.ManyServiceProvidersCreatedWarning)) + .UseSqlite(connection))); + + return services; + } + + private static DbContextOptions<TContext> DbContextOptionsFactory<TContext>( + IServiceProvider applicationServiceProvider, + Action<IServiceProvider, DbContextOptionsBuilder> optionsAction) + where TContext : DbContext + { + var builder = new DbContextOptionsBuilder<TContext>( + new DbContextOptions<TContext>(new Dictionary<Type, IDbContextOptionsExtension>())); + + builder.UseApplicationServiceProvider(applicationServiceProvider); + + optionsAction?.Invoke(applicationServiceProvider, builder); + + return builder.Options; + } + + private void EnsureDatabaseCreated(IServiceProvider services) + { + using var scope = services.CreateScope(); + + var applicationDbContext = scope.ServiceProvider.GetService<ApplicationDbContext>(); + if (applicationDbContext?.Database?.GetPendingMigrations()?.Any() == true) + { + applicationDbContext?.Database?.Migrate(); + } + } + + private class JwtPayload + { + [JsonPropertyName("iss")] + public string Issuer { get; set; } + + [JsonPropertyName("aud")] + public string Audience { get; set; } + + [JsonPropertyName("client_id")] + public string ClientId { get; set; } + + [JsonPropertyName("sub")] + public string Subject { get; set; } + + [JsonPropertyName("idp")] + public string IdentityProvider { get; set; } + + [JsonPropertyName("scope")] + public string[] Scopes { get; set; } + } + } +} diff --git a/src/Components/test/E2ETest/Tests/WebAssemblyConfigurationHostedTest.cs b/src/Components/test/E2ETest/Tests/WebAssemblyConfigurationHostedTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..245c4c3b61834e30e7f7b1f4e31df43035f02474 --- /dev/null +++ b/src/Components/test/E2ETest/Tests/WebAssemblyConfigurationHostedTest.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using BasicTestApp; +using Microsoft.AspNetCore.Components.E2ETest; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.Tests +{ + public class WebAssemblyConfigurationHostedTest : ServerTestBase<BasicTestAppServerSiteFixture<TestServer.ClientStartup>> + { + private IWebElement _appElement; + + public WebAssemblyConfigurationHostedTest( + BrowserFixture browserFixture, + BasicTestAppServerSiteFixture<TestServer.ClientStartup> serverFixture, + ITestOutputHelper output) : + base(browserFixture, serverFixture, output) + { + } + + protected override void InitializeAsyncCore() + { + base.InitializeAsyncCore(); + + Navigate(ServerPathBase, noReload: false); + _appElement = Browser.MountTestComponent<ConfigurationComponent>(); + } + + [Fact] + public void WebAssemblyConfiguration_Works() + { + // Verify values from the default 'appsettings.json' are read. + Browser.Equal("Default key1-value", () => _appElement.FindElement(By.Id("key1")).Text); + + // Verify values overriden by an environment specific 'appsettings.$(Environment).json are read + Assert.Equal("Prod key2-value", _appElement.FindElement(By.Id("key2")).Text); + + // Lastly for sanity, make sure values specified in an environment specific 'appsettings.$(Environment).json are read + Assert.Equal("Prod key3-value", _appElement.FindElement(By.Id("key3")).Text); + } + + [Fact] + public void WebAssemblyHostingEnvironment_Works() + { + // Verify values from the default 'appsettings.json' are read. + Browser.Equal("Production", () => _appElement.FindElement(By.Id("environment")).Text); + } + } +} diff --git a/src/Components/test/E2ETest/Tests/WebAssemblyConfigurationTest.cs b/src/Components/test/E2ETest/Tests/WebAssemblyConfigurationTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..3dc2a0a11c7e2d78285971ad5034e1253bc29316 --- /dev/null +++ b/src/Components/test/E2ETest/Tests/WebAssemblyConfigurationTest.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using BasicTestApp; +using Microsoft.AspNetCore.Components.E2ETest; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using TestServer; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.Tests +{ + public class WebAssemblyConfigurationTest : ServerTestBase<DevHostServerFixture<BasicTestApp.Program>> + { + private IWebElement _appElement; + + public WebAssemblyConfigurationTest( + BrowserFixture browserFixture, + DevHostServerFixture<BasicTestApp.Program> serverFixture, + ITestOutputHelper output) : + base(browserFixture, serverFixture, output) + { + _serverFixture.PathBase = "/subdir"; + } + + protected override void InitializeAsyncCore() + { + base.InitializeAsyncCore(); + + Navigate(ServerPathBase, noReload: false); + _appElement = Browser.MountTestComponent<ConfigurationComponent>(); + } + + [Fact] + public void WebAssemblyConfiguration_Works() + { + // Verify values from the default 'appsettings.json' are read. + Browser.Equal("Default key1-value", () => _appElement.FindElement(By.Id("key1")).Text); + + // Verify values overriden by an environment specific 'appsettings.$(Environment).json are read + Assert.Equal("Development key2-value", _appElement.FindElement(By.Id("key2")).Text); + + // Lastly for sanity, make sure values specified in an environment specific 'appsettings.$(Environment).json are read + Assert.Equal("Development key3-value", _appElement.FindElement(By.Id("key3")).Text); + } + + [Fact] + public void WebAssemblyConfiguration_ReloadingWorks() + { + // Verify values from the default 'appsettings.json' are read. + Browser.Equal("Default key1-value", () => _appElement.FindElement(By.Id("key1")).Text); + + // Change the value of key1 using the form in the UI + var input = _appElement.FindElement(By.Id("key1-input")); + input.SendKeys("newValue"); + var submit = _appElement.FindElement(By.Id("trigger-change")); + submit.Click(); + + // Asser that the value of the key has been updated + Browser.Equal("newValue", () => _appElement.FindElement(By.Id("key1")).Text); + } + + [Fact] + public void WebAssemblyHostingEnvironment_Works() + { + // Dev-Server defaults to Development. It's in the name! + Browser.Equal("Development", () => _appElement.FindElement(By.Id("environment")).Text); + } + } +} diff --git a/src/Components/test/E2ETest/Tests/WebAssemblyGlobalizationTest.cs b/src/Components/test/E2ETest/Tests/WebAssemblyGlobalizationTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..bf5fcb6f62b063fbcf5e2f7456eedb9df3cb9968 --- /dev/null +++ b/src/Components/test/E2ETest/Tests/WebAssemblyGlobalizationTest.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using BasicTestApp; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.Components.E2ETests.Tests; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests +{ + // For now this is limited to server-side execution because we don't have the ability to set the + // culture in client-side Blazor. + public class WebAssemblyGlobalizationTest : GlobalizationTest<ToggleExecutionModeServerFixture<Program>> + { + public WebAssemblyGlobalizationTest( + BrowserFixture browserFixture, + ToggleExecutionModeServerFixture<Program> serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + } + + protected override void SetCulture(string culture) + { + Navigate($"{ServerPathBase}/?culture={culture}", noReload: false); + + // That should have triggered a page load, so wait for the main test selector to come up. + Browser.MountTestComponent<GlobalizationBindCases>(); + Browser.Exists(By.Id("globalization-cases")); + + var cultureDisplay = Browser.Exists(By.Id("culture-name-display")); + Assert.Equal($"Culture is: {culture}", cultureDisplay.Text); + } + } +} diff --git a/src/Components/test/E2ETest/Tests/WebAssemblyLocalizationTest.cs b/src/Components/test/E2ETest/Tests/WebAssemblyLocalizationTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..81c99ccf677a4de62c7e5f071d98a9fc059b883e --- /dev/null +++ b/src/Components/test/E2ETest/Tests/WebAssemblyLocalizationTest.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using BasicTestApp; +using Microsoft.AspNetCore.Components.E2ETest; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.Tests +{ + public class WebAssemblyLocalizationTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>> + { + public WebAssemblyLocalizationTest( + BrowserFixture browserFixture, + ToggleExecutionModeServerFixture<Program> serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + } + + [Theory] + [InlineData("en-US", "Hello!")] + [InlineData("fr-FR", "Bonjour!")] + public void CanSetCultureAndReadLocalizedResources(string culture, string message) + { + Navigate($"{ServerPathBase}/?culture={culture}", noReload: false); + + Browser.MountTestComponent<LocalizedText>(); + + var cultureDisplay = Browser.Exists(By.Id("culture-name-display")); + Assert.Equal($"Culture is: {culture}", cultureDisplay.Text); + + var messageDisplay = Browser.FindElement(By.Id("message-display")); + Assert.Equal(message, messageDisplay.Text); + } + } +} diff --git a/src/Components/test/E2ETest/Tests/WebAssemblyLoggingTest.cs b/src/Components/test/E2ETest/Tests/WebAssemblyLoggingTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..5d83568f5046ab29f0985532025a928865d3f3f8 --- /dev/null +++ b/src/Components/test/E2ETest/Tests/WebAssemblyLoggingTest.cs @@ -0,0 +1,133 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using BasicTestApp; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.Tests +{ + public class WebAssemblyLoggingTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>> + { + public WebAssemblyLoggingTest( + BrowserFixture browserFixture, + ToggleExecutionModeServerFixture<Program> serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + } + + protected override void InitializeAsyncCore() + { + Navigate(ServerPathBase, noReload: false); + Browser.MountTestComponent<ErrorComponent>(); + Browser.Exists(By.Id("blazor-error-ui")); + + var errorUi = Browser.FindElement(By.Id("blazor-error-ui")); + Assert.Equal("none", errorUi.GetCssValue("display")); + } + + [Fact] + public void LogsSimpleExceptionsUsingLogger() + { + Browser.FindElement(By.Id("throw-simple-exception")).Click(); + Browser.Exists(By.CssSelector("#blazor-error-ui[style='display: block;']"), TimeSpan.FromSeconds(10)); + AssertLogContainsCriticalMessages( + "crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]", + "[Custom logger] Unhandled exception rendering component: Doing something that won't work!", + "System.InvalidTimeZoneException: Doing something that won't work!", + "at BasicTestApp.ErrorComponent.ThrowSimple"); + } + + [Fact] + public void LogsInnerExceptionsUsingLogger() + { + Browser.FindElement(By.Id("throw-inner-exception")).Click(); + Browser.Exists(By.CssSelector("#blazor-error-ui[style='display: block;']"), TimeSpan.FromSeconds(10)); + AssertLogContainsCriticalMessages( + "crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]", + "[Custom logger] Unhandled exception rendering component: Here is the outer exception", + "System.InvalidTimeZoneException: Here is the outer exception ---> System.ArithmeticException: Here is the inner exception", + "at BasicTestApp.ErrorComponent.ThrowInner"); + } + + [Fact] + public void LogsAggregateExceptionsUsingLogger() + { + Browser.FindElement(By.Id("throw-aggregate-exception")).Click(); + Browser.Exists(By.CssSelector("#blazor-error-ui[style='display: block;']"), TimeSpan.FromSeconds(10)); + AssertLogContainsCriticalMessages( + "crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]", + "[Custom logger] Unhandled exception rendering component: Aggregate exception 1", + "System.InvalidTimeZoneException: Aggregate exception 1", + "[Custom logger] Unhandled exception rendering component: Aggregate exception 2", + "System.InvalidTimeZoneException: Aggregate exception 2", + "[Custom logger] Unhandled exception rendering component: Aggregate exception 3", + "System.InvalidTimeZoneException: Aggregate exception 3"); + } + + [Fact] + public void LogsUsingCustomLogger() + { + Browser.MountTestComponent<LoggingComponent>(); + Browser.Exists(By.Id("blazor-error-ui")); + Browser.Exists(By.Id("log-trace")); + + ((IJavaScriptExecutor)Browser).ExecuteScript("console.info('Test log message')"); + + // None of these severity levels are displayed by default, so at the end + // we'll continue to see "Test log message" as the most recent output + Browser.FindElement(By.Id("log-none")).Click(); + Browser.FindElement(By.Id("log-trace")).Click(); + Browser.FindElement(By.Id("log-debug")).Click(); + Browser.FindElement(By.Id("log-information")).Click(); + // The Warning minimum log-level is only set on the PrependMessage + // logger so the last info log will be processed by the default + // logger but not the PrependMessage one. + AssertLastLogMessage(LogLevel.Info, "info: BasicTestApp.ErrorComponent[0]"); + + // These severity levels are displayed + Browser.FindElement(By.Id("log-warning")).Click(); + AssertLastLogMessage(LogLevel.Warning, "[Custom logger] This is a Warning message with count=5"); + Browser.FindElement(By.Id("log-error")).Click(); + AssertLastLogMessage(LogLevel.Severe, "[Custom logger] This is a Error message with count=6"); + + // All the preceding levels don't cause the error UI to appear + var errorUi = Browser.FindElement(By.Id("blazor-error-ui")); + Assert.Equal("none", errorUi.GetCssValue("display")); + + // ... but "Critical" level does + Browser.FindElement(By.Id("log-critical")).Click(); + AssertLastLogMessage(LogLevel.Severe, "[Custom logger] This is a Critical message with count=7"); + Assert.Equal("block", errorUi.GetCssValue("display")); + } + + void AssertLastLogMessage(LogLevel level, string message) + { + var log = Browser.Manage().Logs.GetLog(LogType.Browser); + var lastEntry = log[log.Count - 1]; + Assert.Equal(level, lastEntry.Level); + + // Selenium prefixes the message with various bits of internal info, so use "Contains" + Assert.Contains(message, lastEntry.Message); + } + + void AssertLogContainsCriticalMessages(params string[] messages) + { + var log = Browser.Manage().Logs.GetLog(LogType.Browser); + foreach (var message in messages) + { + Assert.Contains(log, entry => + { + return entry.Level == LogLevel.Severe + && entry.Message.Contains(message); + }); + } + } + } +} diff --git a/src/Components/test/E2ETest/Tests/WebAssemblyStringComparisonTest.cs b/src/Components/test/E2ETest/Tests/WebAssemblyStringComparisonTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..9a38268403459743ba59b6271ee5d8468d08d42e --- /dev/null +++ b/src/Components/test/E2ETest/Tests/WebAssemblyStringComparisonTest.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using BasicTestApp; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.E2ETesting; +using OpenQA.Selenium; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.Tests +{ + public class WebAssemblyStringComparisonTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>> + { + public WebAssemblyStringComparisonTest( + BrowserFixture browserFixture, + ToggleExecutionModeServerFixture<Program> serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + } + + [Fact] + public void InvariantCultureWorksAsExpected() + { + Navigate(ServerPathBase, noReload: false); + Browser.MountTestComponent<StringComparisonComponent>(); + + var result = Browser.Exists(By.Id("results")); + + Assert.Equal("Ordinal: False Invariant: True", result.Text); + } + } +} diff --git a/src/Components/test/testassets/BasicTestApp/AuthTest/ServerAuthenticationStateProvider.cs b/src/Components/test/testassets/BasicTestApp/AuthTest/ServerAuthenticationStateProvider.cs index 31316f85c1891451884da535fc352f32f419b62e..4f61a171b333bdf3b94b715bebc838e61c79454b 100644 --- a/src/Components/test/testassets/BasicTestApp/AuthTest/ServerAuthenticationStateProvider.cs +++ b/src/Components/test/testassets/BasicTestApp/AuthTest/ServerAuthenticationStateProvider.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Net.Http; +using System.Net.Http.Json; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; @@ -25,7 +26,7 @@ namespace BasicTestApp.AuthTest public override async Task<AuthenticationState> GetAuthenticationStateAsync() { var uri = new Uri(_httpClient.BaseAddress, "/subdir/api/User"); - var data = await _httpClient.GetJsonAsync<ClientSideAuthenticationStateData>(uri.AbsoluteUri); + var data = await _httpClient.GetFromJsonAsync<ClientSideAuthenticationStateData>(uri.AbsoluteUri); ClaimsIdentity identity; if (data.IsAuthenticated) { diff --git a/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj b/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj index 9914ec452179c2a8c8c4c32a94d6c5e89a45f544..1cba39fcdee3a6242c0bfa0120e4d5c23a68b797 100644 --- a/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj +++ b/src/Components/test/testassets/BasicTestApp/BasicTestApp.csproj @@ -10,14 +10,16 @@ <!-- Resx generation on Resources.resx only --> <GenerateResxSource>false</GenerateResxSource> + + <FixupWebAssemblyHttpHandlerReference>true</FixupWebAssemblyHttpHandlerReference> </PropertyGroup> <ItemGroup> <Reference Include="System.ComponentModel" /> - <Reference Include="Microsoft.AspNetCore.Blazor" /> - <Reference Include="Microsoft.AspNetCore.Blazor.HttpClient" /> + <Reference Include="System.Net.Http.Json" /> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly" /> <Reference Include="Microsoft.AspNetCore.Components.Authorization" /> - <Reference Include="Microsoft.AspNetCore.Blazor.DataAnnotations.Validation" /> + <Reference Include="Microsoft.Extensions.Logging.Configuration" /> </ItemGroup> <ItemGroup> diff --git a/src/Components/test/testassets/BasicTestApp/ConfigurationComponent.razor b/src/Components/test/testassets/BasicTestApp/ConfigurationComponent.razor new file mode 100644 index 0000000000000000000000000000000000000000..13b95fb0376ffc2af4cf22106b13f3db0270543c --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/ConfigurationComponent.razor @@ -0,0 +1,25 @@ +@inject Microsoft.Extensions.Configuration.IConfiguration Config +@inject Microsoft.AspNetCore.Components.WebAssembly.Hosting.IWebAssemblyHostEnvironment HostEnvironment + +<ul> + <li id="key1">@Config["key1"]</li> + <li id="key2">@Config["key2"]</li> + <li id="key3">@Config["key3"]</li> +</ul> + +<div id="environment">@HostEnvironment.Environment</div> + +<p> + <input id="key1-input" @bind-value=newKey1 @bind-value:event="oninput" /> + <button id="trigger-change" @onclick="@(() => TriggerChange())">Change key1</button> +</p> + +@code { + string newKey1 { get; set; } + + void TriggerChange() + { + Config["key1"] = newKey1; + } +} + diff --git a/src/Components/test/testassets/BasicTestApp/ErrorComponent.razor b/src/Components/test/testassets/BasicTestApp/ErrorComponent.razor index 62855e8935cf25838a430cf088765b1316b72f70..f0104fa4673538d5b9f22bed27c5fe45a756332e 100644 --- a/src/Components/test/testassets/BasicTestApp/ErrorComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/ErrorComponent.razor @@ -1,16 +1,34 @@ <div> - <h2>Error throwing button</h2> + <h2>Error throwing buttons</h2> <p> - <button @onclick="@(IncrementCount)">Click me</button> + <button id="throw-simple-exception" @onclick="@(ThrowSimple)">Throw simple exception</button> + <button id="throw-inner-exception" @onclick="@(ThrowInner)">Throw with inner exception</button> + <button id="throw-aggregate-exception" @onclick="@(ThrowAggregate)">Throw aggregate exception</button> </p> </div> @code { int currentCount = 0; - void IncrementCount() + void ThrowSimple() { currentCount++; - throw new NotImplementedException("Doing something that won't work!"); + throw new InvalidTimeZoneException("Doing something that won't work!"); + } + + void ThrowInner() + { + currentCount++; + throw new InvalidTimeZoneException("Here is the outer exception", + new ArithmeticException("Here is the inner exception")); + } + + void ThrowAggregate() + { + currentCount++; + throw new AggregateException( + new InvalidTimeZoneException("Aggregate exception 1"), + new InvalidTimeZoneException("Aggregate exception 2"), + new InvalidTimeZoneException("Aggregate exception 3")); } } diff --git a/src/Components/test/testassets/BasicTestApp/FormsTest/ExperimentalValidationComponent.razor b/src/Components/test/testassets/BasicTestApp/FormsTest/ExperimentalValidationComponent.razor deleted file mode 100644 index fe3df5382e9de777aaed27810386dbf5ad16b395..0000000000000000000000000000000000000000 --- a/src/Components/test/testassets/BasicTestApp/FormsTest/ExperimentalValidationComponent.razor +++ /dev/null @@ -1,185 +0,0 @@ -@using System.ComponentModel.DataAnnotations -@using Microsoft.AspNetCore.Components.Forms - - <p> - This component is used to verify the use of the experimental ObjectGraphDataAnnotationsValidator type with IValidatableObject and deep validation, as well - as the ComparePropertyAttribute. - </p> - -<EditForm Model="@model" OnValidSubmit="@HandleValidSubmit"> - <ObjectGraphDataAnnotationsValidator /> - - <p class="name"> - Name: <InputText @bind-Value="model.Recipient" placeholder="Enter the recipient" /> - <ValidationMessage For="@(() => model.Recipient)" /> - </p> - - <p class="email"> - Email: <InputText @bind-Value="model.EmailAddress" /> - <ValidationMessage For="@(() => model.EmailAddress)" /> - </p> - - <p class="confirm-email"> - Confirm Email: <InputText @bind-Value="model.ConfirmEmailAddress" /> - <ValidationMessage For="@(() => model.ConfirmEmailAddress)" /> - </p> - - <fieldset> - <legend>Items to deliver</legend> - <p> - <button id="addItem" type="button" @onclick="AddItem">Add Item</button> - </p> - <ul class="items"> - @foreach (var item in model.Items) - { - <li> - <div style="display: inline-flex; flex-direction: row"> - <div style="flex-grow: 1" class="description"> - <InputText @bind-Value="item.Description" placeholder="Description" /> - <ValidationMessage For="@(() => item.Description)" /> - </div> - - <div style="flex-grow: 1" class="weight"> - <InputNumber @bind-Value="item.Weight" /> - <ValidationMessage For="@(() => item.Weight)" /> - </div> - <div style="flex-grow: 1" class="item-error"> - <ValidationSummary Model="item" /> - </div> - </div> - </li> - } - </ul> - </fieldset> - - <fieldset> - <legend>Shipping details</legend> - <p class="street"> - Street Address: <InputText @bind-Value="model.Address.Street" /> - <ValidationMessage For="@(() => model.Address.Street)" /> - </p> - <p class="zip"> - Zip Code: <InputText @bind-Value="model.Address.ZipCode" /> - <ValidationMessage For="@(() => model.Address.ZipCode)" /> - </p> - <p class="country"> - Country: - <InputSelect @bind-Value="model.Address.Country"> - <option></option> - <option value="@Country.Gondor">@Country.Gondor</option> - <option value="@Country.Mordor">@Country.Mordor</option> - <option value="@Country.Rohan">@Country.Rohan</option> - <option value="@Country.Shire">@Country.Shire</option> - </InputSelect> - <ValidationMessage For="@(() => model.Address.Country)" /> - </p> - <p class="address-validation"> - <ValidationSummary Model="model.Address" /> - </p> - </fieldset> - - <div class="model-errors"> - <ValidationSummary Model="model"/> - </div> - <button type="submit">Submit</button> - - <div class="all-errors"> - <ValidationSummary /> - </div> - -</EditForm> - -<ul class="submission-log"> - @foreach (var entry in submissionLog) - { - <li>@entry</li> - } -</ul> - -@code { - Delivery model = new Delivery(); - - public class Delivery : IValidatableObject - { - [Required(ErrorMessage = "Enter a name")] - public string Recipient { get; set; } - - [Required(ErrorMessage = "Enter an email")] - [EmailAddress(ErrorMessage = "Enter a valid email address")] - public string EmailAddress { get; set; } - - [CompareProperty(nameof(EmailAddress))] - [Display(Name = "Confirm email address")] - public string ConfirmEmailAddress { get; set; } - - [ValidateComplexType] - public Address Address { get; } = new Address(); - - [ValidateComplexType] - public List<Item> Items { get; } = new List<Item> - { - new Item(), - }; - - public IEnumerable<ValidationResult> Validate(ValidationContext context) - { - if (Address.Street == "Mount Doom" && Items.Any(i => i.Description == "The One Ring")) - { - yield return new ValidationResult("Some items in your list cannot be delivered."); - } - } - } - - public class Address : IValidatableObject - { - [Required(ErrorMessage = "A street address is required.")] - public string Street { get; set; } - - public string ZipCode { get; set; } - - [EnumDataType(typeof(Country))] - public Country Country { get; set; } - - public IEnumerable<ValidationResult> Validate(ValidationContext context) - { - if (Country == Country.Mordor && string.IsNullOrEmpty(ZipCode)) - { - yield return new ValidationResult("A ZipCode is required", new[] { nameof(ZipCode) }); - } - } - } - - [CustomValidation(typeof(Item), nameof(Item.CustomValidate))] - public class Item - { - [Required(ErrorMessage = "Description is required.")] - public string Description { get; set; } - - [Range(0.1, 50, ErrorMessage = "Items must weigh between 0.1 and 5")] - public double Weight { get; set; } = 1; - - public static ValidationResult CustomValidate(Item item, ValidationContext context) - { - if (item.Weight < 2.0 && item.Description.StartsWith("Fragile")) - { - return new ValidationResult("Fragile items must be placed in secure containers"); - } - - return ValidationResult.Success; - } - } - - public enum Country { Gondor, Mordor, Rohan, Shire } - - List<string> submissionLog = new List<string>(); - - void HandleValidSubmit() - { - submissionLog.Add("OnValidSubmit"); - } - - void AddItem() - { - model.Items.Add(new Item()); - } -} diff --git a/src/Components/test/testassets/BasicTestApp/FormsTest/SimpleValidationComponent.razor b/src/Components/test/testassets/BasicTestApp/FormsTest/SimpleValidationComponent.razor index 2c9a4d2456792a231d90384a0748d43947ce0b37..35cbdcd89361a339491a95a24b99735cf951ca3d 100644 --- a/src/Components/test/testassets/BasicTestApp/FormsTest/SimpleValidationComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/FormsTest/SimpleValidationComponent.razor @@ -2,14 +2,7 @@ @using Microsoft.AspNetCore.Components.Forms <EditForm Model="@this" OnValidSubmit="@HandleValidSubmit" OnInvalidSubmit="@HandleInvalidSubmit" autocomplete="off"> - @if (UseExperimentalValidator) - { - <ObjectGraphDataAnnotationsValidator /> - } - else - { - <DataAnnotationsValidator /> - } + <DataAnnotationsValidator /> <p class="user-name"> User name: <input @bind="UserName" class="@context.FieldCssClass(() => UserName)" /> diff --git a/src/Components/test/testassets/BasicTestApp/FormsTest/SimpleValidationComponentUsingExperimentalValidator.cs b/src/Components/test/testassets/BasicTestApp/FormsTest/SimpleValidationComponentUsingExperimentalValidator.cs deleted file mode 100644 index 90484aad05d001d86de6999bdf4911de65a8ad38..0000000000000000000000000000000000000000 --- a/src/Components/test/testassets/BasicTestApp/FormsTest/SimpleValidationComponentUsingExperimentalValidator.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BasicTestApp.FormsTest -{ - public class TypicalValidationComponentUsingExperimentalValidator : TypicalValidationComponent - { - protected override bool UseExperimentalValidator => true; - } -} diff --git a/src/Components/test/testassets/BasicTestApp/FormsTest/TypicalValidationComponent.razor b/src/Components/test/testassets/BasicTestApp/FormsTest/TypicalValidationComponent.razor index 90b2f6a26b65939dcce72cca37994cd46e023401..69c2f585017cbc8617f716b2388571c13e0c3f38 100644 --- a/src/Components/test/testassets/BasicTestApp/FormsTest/TypicalValidationComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/FormsTest/TypicalValidationComponent.razor @@ -2,14 +2,7 @@ @using Microsoft.AspNetCore.Components.Forms <EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit"> - @if (UseExperimentalValidator) - { - <ObjectGraphDataAnnotationsValidator /> - } - else - { <DataAnnotationsValidator /> - } <p class="name"> Name: <InputText @bind-Value="person.Name" placeholder="Enter your name" /> diff --git a/src/Components/test/testassets/BasicTestApp/FormsTest/TypicalValidationComponentUsingExperimentalValidator.cs b/src/Components/test/testassets/BasicTestApp/FormsTest/TypicalValidationComponentUsingExperimentalValidator.cs deleted file mode 100644 index 9ede005a8aa66a1f7e6368c59149963e02b1a48a..0000000000000000000000000000000000000000 --- a/src/Components/test/testassets/BasicTestApp/FormsTest/TypicalValidationComponentUsingExperimentalValidator.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BasicTestApp.FormsTest -{ - public class SimpleValidationComponentUsingExperimentalValidator : SimpleValidationComponent - { - protected override bool UseExperimentalValidator => true; - } -} diff --git a/src/Components/test/testassets/BasicTestApp/HttpClientTest/CookieCounterComponent.razor b/src/Components/test/testassets/BasicTestApp/HttpClientTest/CookieCounterComponent.razor index c604c884868eefe5e7157400f0a5d9538325a33e..7497f884f95c3e494f64ea2f381a514d7302ae88 100644 --- a/src/Components/test/testassets/BasicTestApp/HttpClientTest/CookieCounterComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/HttpClientTest/CookieCounterComponent.razor @@ -1,3 +1,5 @@ +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using System.Net.Http @inject System.Net.Http.HttpClient Http <h1>Cookie counter</h1> @@ -32,7 +34,11 @@ async Task DoRequest(string url) { requestInProgress = true; - responseText = await Http.GetStringAsync(testServerBaseUrl + url); + var request = new HttpRequestMessage(HttpMethod.Get, testServerBaseUrl + url); + request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include); + using var response = await Http.SendAsync(request); + + responseText = await response.Content.ReadAsStringAsync(); requestInProgress = false; } } diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index eff29276a459f98c9232cd4e586dc81e3deec3c0..401a79c367e461097868a4744f55ec84507fbf13 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -12,6 +12,7 @@ <option value="BasicTestApp.CascadingValueTest.CascadingValueSupplier">Cascading values</option> <option value="BasicTestApp.ComponentRefComponent">Component ref component</option> <option value="BasicTestApp.ConcurrentRenderParent">Concurrent rendering</option> + <option value="BasicTestApp.ConfigurationComponent">Configuration</option> <option value="BasicTestApp.CounterComponent">Counter</option> <option value="BasicTestApp.CounterComponentUsingChild">Counter using child component</option> <option value="BasicTestApp.CounterComponentWrapper">Counter wrapped in parent</option> @@ -33,7 +34,6 @@ <option value="BasicTestApp.FormsTest.SimpleValidationComponentUsingExperimentalValidator">Simple validation using experimental validator</option> <option value="BasicTestApp.FormsTest.TypicalValidationComponent">Typical validation</option> <option value="BasicTestApp.FormsTest.TypicalValidationComponentUsingExperimentalValidator">Typical validation using experimental validator</option> - <option value="BasicTestApp.FormsTest.ExperimentalValidationComponent">Experimental validation</option> <option value="BasicTestApp.GlobalizationBindCases">Globalization Bind Cases</option> <option value="BasicTestApp.HierarchicalImportsTest.Subdir.ComponentUsingImports">Imports statement</option> <option value="BasicTestApp.HtmlBlockChildContent">ChildContent HTML Block</option> @@ -45,12 +45,14 @@ <option value="BasicTestApp.InputEventComponent">Input events</option> <option value="BasicTestApp.InteropComponent">Interop component</option> <option value="BasicTestApp.InteropOnInitializationComponent">Interop on initialization</option> + <option value="BasicTestApp.JsonSerializationCases">JSON serialization</option> <option value="BasicTestApp.KeyCasesComponent">Key cases</option> <option value="BasicTestApp.KeyPressEventComponent">Key press event</option> <option value="BasicTestApp.LaggyTypingComponent">Laggy typing</option> <option value="BasicTestApp.LimitCounterComponent">Limit counter component</option> <option value="BasicTestApp.LocalizedText">Localized Text</option> <option value="BasicTestApp.LogicalElementInsertionCases">Logical element insertion cases</option> + <option value="BasicTestApp.LoggingComponent">Logging</option> <option value="BasicTestApp.LongRunningInterop">Long running interop</option> <option value="BasicTestApp.MarkupBlockComponent">Markup blocks</option> <option value="BasicTestApp.MouseEventComponent">Mouse events</option> @@ -60,14 +62,15 @@ <option value="BasicTestApp.ParentChildComponent">Parent component with child</option> <option value="BasicTestApp.PropertiesChangedHandlerParent">Parent component that changes parameters on child</option> <option value="BasicTestApp.RazorTemplates">Razor Templates</option> + <option value="BasicTestApp.Reconnection.ReconnectionComponent">Reconnection server-side blazor</option> <option value="BasicTestApp.RedTextComponent">Red text</option> <option value="BasicTestApp.ReliabilityComponent">Server reliability component</option> <option value="BasicTestApp.RenderFragmentToggler">Render fragment renderer</option> - <option value="BasicTestApp.Reconnection.ReconnectionComponent">Reconnection server-side blazor</option> <option value="BasicTestApp.ReorderingFocusComponent">Reordering focus retention</option> <option value="BasicTestApp.RouterTest.NavigationManagerComponent">NavigationManager Test</option> <option value="BasicTestApp.RouterTest.TestRouter">Router</option> <option value="BasicTestApp.RouterTest.TestRouterWithAdditionalAssembly">Router with additional assembly</option> + <option value="BasicTestApp.StringComparisonComponent">StringComparison</option> <option value="BasicTestApp.SvgComponent">SVG</option> <option value="BasicTestApp.SvgWithChildComponent">SVG with child component</option> <option value="BasicTestApp.TextOnlyComponent">Plain text</option> diff --git a/src/Components/test/testassets/BasicTestApp/InteropComponent.razor b/src/Components/test/testassets/BasicTestApp/InteropComponent.razor index 0024d8bb751bb081dd796d8830434425e7aee36c..9e2167b4b0c315ed71ce85d9f91bea2e029b7c88 100644 --- a/src/Components/test/testassets/BasicTestApp/InteropComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/InteropComponent.razor @@ -67,7 +67,7 @@ public async Task InvokeInteropAsync() { - var shouldSupportSyncInterop = RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY")); + var shouldSupportSyncInterop = RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER")); var testDTOTOPassByRef = new TestDTO(nonSerializedValue: 123); var instanceMethodsTarget = new JavaScriptInterop(); var genericType = new JavaScriptInterop.GenericType<string> { Value = "Initial value" }; diff --git a/src/Components/test/testassets/BasicTestApp/JsonSerializationCases.razor b/src/Components/test/testassets/BasicTestApp/JsonSerializationCases.razor new file mode 100644 index 0000000000000000000000000000000000000000..7f1b227f5831960b99200b3835533d9b1232dacc --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/JsonSerializationCases.razor @@ -0,0 +1,35 @@ +@using System.Text.Json +@using System.Text.Json.Serialization + +<h3 id="json-serialization-cases">JSON serialization cases</h3> + +<p> + Most aspects of System.Text.Json don't need to be tested here, as that's an external library. However some cases + are worth verifying under WebAssembly/linking specifically. +</p> + +<p>Name: <strong id="deserialized-name">@deserializedPerson.Name</strong></p> +<p>Age: <strong id="deserialized-age">@deserializedPerson.Age</strong></p> +<p>Mood: <strong id="deserialized-mood">@deserializedPerson.Mood</strong></p> + +@code { + Person deserializedPerson; + + protected override void OnInitialized() + { + // Round-trip some data + var input = new Person { Name = "Lord Smythe", Age = 68, Mood = EmotionalState.Vexed }; + var serializedJson = JsonSerializer.Serialize(input); + deserializedPerson = JsonSerializer.Deserialize<Person>(serializedJson); + } + + public class Person + { + public string Name { get; set; } + public int Age { get; set; } + public EmotionalState Mood { get; set; } + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum EmotionalState { Sombre, Vexed, Irate, Tormented } +} diff --git a/src/Components/test/testassets/BasicTestApp/LoggingComponent.razor b/src/Components/test/testassets/BasicTestApp/LoggingComponent.razor new file mode 100644 index 0000000000000000000000000000000000000000..6ac41d792cc3625fc34bf14a8dc691975ae08417 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/LoggingComponent.razor @@ -0,0 +1,25 @@ +@using Microsoft.Extensions.Logging +@inject ILoggerFactory LoggerFactory + +<h2>Logging buttons</h2> +<p> + <button id="log-none" @onclick="@(() => Log(LogLevel.None))">Log None</button> + <button id="log-trace" @onclick="@(() => Log(LogLevel.Trace))">Log Trace</button> + <button id="log-debug" @onclick="@(() => Log(LogLevel.Debug))">Log Debug</button> + <button id="log-information" @onclick="@(() => Log(LogLevel.Information))">Log Information</button> + <button id="log-warning" @onclick="@(() => Log(LogLevel.Warning))">Log Warning</button> + <button id="log-error" @onclick="@(() => Log(LogLevel.Error))">Log Error</button> + <button id="log-critical" @onclick="@(() => Log(LogLevel.Critical))">Log Critical</button> +</p> + +@code { + int currentCount = 0; + + void Log(LogLevel level) + { + currentCount++; + + var logger = LoggerFactory.CreateLogger<ErrorComponent>(); + logger.Log(level, $"This is a {level} message with count={{count}}", currentCount); + } +} diff --git a/src/Components/test/testassets/BasicTestApp/PrependMessageLoggerProvider.cs b/src/Components/test/testassets/BasicTestApp/PrependMessageLoggerProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..35d4fd72bb98fce1e49321830948523fddcca690 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/PrependMessageLoggerProvider.cs @@ -0,0 +1,66 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; +using Microsoft.AspNetCore.Components.WebAssembly.Services; +using Microsoft.JSInterop; + +namespace BasicTestApp +{ + [ProviderAlias("PrependMessage")] + internal class PrependMessageLoggerProvider : ILoggerProvider + { + ILogger _logger; + string _message; + ILogger _defaultLogger; + private bool _disposed = false; + + public PrependMessageLoggerProvider(string message, IJSRuntime runtime) + { + _message = message; + _defaultLogger = new WebAssemblyConsoleLogger<object>(runtime); + } + + public ILogger CreateLogger(string categoryName) + { + if (_logger == null) + { + _logger = new PrependMessageLogger(_message, _defaultLogger); + } + return _logger; + } + + public void Dispose() + { + if (!_disposed) + { + _logger = null; + } + _disposed = true; + } + + private class PrependMessageLogger : ILogger + { + private readonly string _message; + private readonly ILogger _underlyingLogger; + + public PrependMessageLogger(string message, ILogger underlyingLogger) + { + _message = message; + _underlyingLogger = underlyingLogger; + } + + public IDisposable BeginScope<TState>(TState state) + => _underlyingLogger.BeginScope(state); + + public bool IsEnabled(LogLevel logLevel) + => _underlyingLogger.IsEnabled(logLevel); + + public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) + => _underlyingLogger.Log(logLevel, eventId, state, exception, + (state, exception) => $"[{_message}] {formatter(state, exception)}"); + } + } +} diff --git a/src/Components/test/testassets/BasicTestApp/Program.cs b/src/Components/test/testassets/BasicTestApp/Program.cs index 0c62f05bd1afa090cea5a3ff8903a0ea6393c2dd..01d7c29f000daba50ad813b265015fe4429dadb6 100644 --- a/src/Components/test/testassets/BasicTestApp/Program.cs +++ b/src/Components/test/testassets/BasicTestApp/Program.cs @@ -3,14 +3,21 @@ using System; using System.Globalization; +using System.Net.Http; using System.Runtime.InteropServices; using System.Threading.Tasks; +using System.Web; using BasicTestApp.AuthTest; -using Microsoft.AspNetCore.Blazor.Hosting; -using Microsoft.AspNetCore.Blazor.Http; +using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.AspNetCore.Components.WebAssembly.Http; +using Microsoft.AspNetCore.Components.WebAssembly.Services; using Microsoft.Extensions.DependencyInjection; -using Mono.WebAssembly.Interop; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Configuration; +using Microsoft.JSInterop; namespace BasicTestApp { @@ -20,20 +27,10 @@ namespace BasicTestApp { await SimulateErrorsIfNeededForTest(); - // We want the culture to be en-US so that the tests for bind can work consistently. - CultureInfo.CurrentCulture = new CultureInfo("en-US"); - var builder = WebAssemblyHostBuilder.CreateDefault(args); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY"))) - { - // Needed because the test server runs on a different port than the client app, - // and we want to test sending/receiving cookies under this config - WebAssemblyHttpMessageHandlerOptions.DefaultCredentials = FetchCredentialsOption.Include; - } - builder.RootComponents.Add<Index>("root"); + builder.Services.AddSingleton(new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddSingleton<AuthenticationStateProvider, ServerAuthenticationStateProvider>(); builder.Services.AddAuthorizationCore(options => { @@ -41,13 +38,48 @@ namespace BasicTestApp policy.RequireAssertion(ctx => ctx.User.Identity.Name?.StartsWith("B") ?? false)); }); - await builder.Build().RunAsync(); + builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging")); + + builder.Logging.Services.AddSingleton<ILoggerProvider, PrependMessageLoggerProvider>(s => + new PrependMessageLoggerProvider(builder.Configuration["Logging:PrependMessage:Message"], s.GetService<IJSRuntime>())); + + + var host = builder.Build(); + ConfigureCulture(host); + + await host.RunAsync(); + } + + private static void ConfigureCulture(WebAssemblyHost host) + { + // In the absence of a specified value, we want the culture to be en-US so that the tests for bind can work consistently. + var culture = new CultureInfo("en-US"); + + Uri uri = null; + try + { + uri = new Uri(host.Services.GetService<NavigationManager>().Uri); + } + catch (ArgumentException) + { + // Some of our tests set this application up incorrectly so that querying NavigationManager.Uri throws. + } + + if (uri != null && HttpUtility.ParseQueryString(uri.Query)["culture"] is string cultureName) + { + culture = new CultureInfo(cultureName); + } + + // CultureInfo.CurrentCulture is async-scoped and will not affect the culture in sibling scopes. + // Use CultureInfo.DefaultThreadCurrentCulture instead to modify the application's default scope. + CultureInfo.DefaultThreadCurrentCulture = culture; + CultureInfo.DefaultThreadCurrentUICulture = culture; } // Supports E2E tests in StartupErrorNotificationTest private static async Task SimulateErrorsIfNeededForTest() { - var currentUrl = new MonoWebAssemblyJSRuntime().Invoke<string>("getCurrentUrl"); + var currentUrl = DefaultWebAssemblyJSRuntime.Instance.Invoke<string>("getCurrentUrl"); if (currentUrl.Contains("error=sync")) { throw new InvalidTimeZoneException("This is a synchronous startup exception"); diff --git a/src/Components/test/testassets/BasicTestApp/StringComparisonComponent.razor b/src/Components/test/testassets/BasicTestApp/StringComparisonComponent.razor new file mode 100644 index 0000000000000000000000000000000000000000..7983a8558e3683b86297ed9e5b82007f83198d35 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/StringComparisonComponent.razor @@ -0,0 +1,11 @@ +@{ + // This test verifies that invariant cultue works correctly in WebAssembly environments. The test case is based on the discussions here: https://stackoverflow.com/a/20085219 + var string1 = "Strasse"; + var string2 = "Straße"; + + var ordinalComparison = string1.Equals(string2, StringComparison.Ordinal); + var invariantComparison = string1.Equals(string2, StringComparison.InvariantCulture); +} + +<div id="results">Ordinal: @ordinalComparison Invariant: @invariantComparison</div> + diff --git a/src/Components/test/testassets/BasicTestApp/wwwroot/appsettings.Development.json b/src/Components/test/testassets/BasicTestApp/wwwroot/appsettings.Development.json new file mode 100644 index 0000000000000000000000000000000000000000..3a9ec3802e537ab8a78b8e53dbb83e99ff34a651 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/wwwroot/appsettings.Development.json @@ -0,0 +1,4 @@ +{ + "key2": "Development key2-value", + "key3": "Development key3-value" +} diff --git a/src/Components/test/testassets/BasicTestApp/wwwroot/appsettings.Production.json b/src/Components/test/testassets/BasicTestApp/wwwroot/appsettings.Production.json new file mode 100644 index 0000000000000000000000000000000000000000..539fe7d8318e91d5befd43ad521fa0dd165af214 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/wwwroot/appsettings.Production.json @@ -0,0 +1,4 @@ +{ + "key2": "Prod key2-value", + "key3": "Prod key3-value" +} diff --git a/src/Components/test/testassets/BasicTestApp/wwwroot/appsettings.json b/src/Components/test/testassets/BasicTestApp/wwwroot/appsettings.json new file mode 100644 index 0000000000000000000000000000000000000000..6534159aa03afdf3c37f4ac715cfac8bbf6de9f0 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/wwwroot/appsettings.json @@ -0,0 +1,12 @@ +{ + "key1": "Default key1-value", + "key2": "Default key2-value", + "Logging": { + "PrependMessage": { + "Message": "Custom logger", + "LogLevel": { + "Default": "Warning" + } + } + } +} diff --git a/src/Components/test/testassets/ComponentsApp.Server/Pages/_Host.cshtml b/src/Components/test/testassets/ComponentsApp.Server/Pages/_Host.cshtml index 9aa4ff85adcb544cae99a8673ddf54629445eba4..5e34a117a55eee098cf51b346fe1c31f4d963fb6 100644 --- a/src/Components/test/testassets/ComponentsApp.Server/Pages/_Host.cshtml +++ b/src/Components/test/testassets/ComponentsApp.Server/Pages/_Host.cshtml @@ -9,7 +9,7 @@ <title>Razor Components</title> <base href="~/" /> <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" /> - <link href="css/site.css" rel="stylesheet" /> + <link href="css/app.css" rel="stylesheet" /> </head> <body> <component type="typeof(App)" render-mode="Server" /> diff --git a/src/Components/test/testassets/ComponentsApp.Server/appsettings.json b/src/Components/test/testassets/ComponentsApp.Server/appsettings.json index 26bb0ac7ac67a92522d39be319e95be1b129b1d0..c851e129f9edeca8fff632d129c42d8e705e1246 100644 --- a/src/Components/test/testassets/ComponentsApp.Server/appsettings.json +++ b/src/Components/test/testassets/ComponentsApp.Server/appsettings.json @@ -1,4 +1,4 @@ -{ +{ "Logging": { "IncludeScopes": false, "Debug": { diff --git a/src/Components/test/testassets/ComponentsApp.Server/wwwroot/css/site.css b/src/Components/test/testassets/ComponentsApp.Server/wwwroot/css/app.css similarity index 100% rename from src/Components/test/testassets/ComponentsApp.Server/wwwroot/css/site.css rename to src/Components/test/testassets/ComponentsApp.Server/wwwroot/css/app.css diff --git a/src/Components/test/testassets/Directory.Build.props b/src/Components/test/testassets/Directory.Build.props new file mode 100644 index 0000000000000000000000000000000000000000..e431fcb4f79d5979b9988b7dc96b84754a377be0 --- /dev/null +++ b/src/Components/test/testassets/Directory.Build.props @@ -0,0 +1,7 @@ +<Project> + <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\..\, Directory.Build.props))\Directory.Build.props" /> + + <PropertyGroup> + <EnableTypeScriptNuGetTarget>true</EnableTypeScriptNuGetTarget> + </PropertyGroup> +</Project> diff --git a/src/Components/test/testassets/TestContentPackage/TestContentPackage.csproj b/src/Components/test/testassets/TestContentPackage/TestContentPackage.csproj index 90e7e7295242c5757b04097b9d953260292c1836..f0cd18aca91428c87cbad34c8761d964c478c134 100644 --- a/src/Components/test/testassets/TestContentPackage/TestContentPackage.csproj +++ b/src/Components/test/testassets/TestContentPackage/TestContentPackage.csproj @@ -7,6 +7,10 @@ <StaticWebAssetBasePath>_content/TestContentPackage</StaticWebAssetBasePath> </PropertyGroup> + <PropertyGroup> + <EnableTypeScriptNuGetTarget>true</EnableTypeScriptNuGetTarget> + </PropertyGroup> + <ItemGroup> <Reference Include="Microsoft.AspNetCore.Components" /> <Reference Include="Microsoft.AspNetCore.Components.Web" /> diff --git a/src/Components/test/testassets/TestServer/AuthenticationStartup.cs b/src/Components/test/testassets/TestServer/AuthenticationStartup.cs index 5c33de8d2d9c61d57a179339a3b70a6b2dc54e10..ca6648d133974a18a6194e2473e682dd7bc0eb7e 100644 --- a/src/Components/test/testassets/TestServer/AuthenticationStartup.cs +++ b/src/Components/test/testassets/TestServer/AuthenticationStartup.cs @@ -27,6 +27,7 @@ namespace TestServer services.AddMvc(); services.AddServerSideBlazor(); + services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(); services.AddAuthorization(options => { @@ -48,8 +49,8 @@ namespace TestServer // Mount the server-side Blazor app on /subdir app.Map("/subdir", app => { + app.UseBlazorFrameworkFiles(); app.UseStaticFiles(); - app.UseClientSideBlazorFiles<BasicTestApp.Program>(); app.UseRouting(); app.UseEndpoints(endpoints => @@ -66,7 +67,7 @@ namespace TestServer public class AuthenticationStartup : AuthenticationStartupBase { public AuthenticationStartup(IConfiguration configuration) - : base(configuration, (endpoints) => endpoints.MapFallbackToClientSideBlazor<BasicTestApp.Program>("index.html")) + : base(configuration, (endpoints) => endpoints.MapFallbackToFile("index.html")) { } } diff --git a/src/Components/test/testassets/TestServer/ClientStartup.cs b/src/Components/test/testassets/TestServer/ClientStartup.cs index 3dd550fc6d7866403cf76d88b499808aa277dc11..b3a16334229b2e242d20b2097727efccd105b00c 100644 --- a/src/Components/test/testassets/TestServer/ClientStartup.cs +++ b/src/Components/test/testassets/TestServer/ClientStartup.cs @@ -37,7 +37,7 @@ namespace TestServer app.Map("/subdir", app => { // Add it before to ensure it takes priority over files in wwwroot - app.UseClientSideBlazorFiles<BasicTestApp.Program>(); + app.UseBlazorFrameworkFiles(); app.UseStaticFiles(); app.UseRouting(); @@ -45,7 +45,7 @@ namespace TestServer { endpoints.MapRazorPages(); endpoints.MapControllers(); - endpoints.MapFallbackToClientSideBlazor<BasicTestApp.Program>("index.html"); + endpoints.MapFallbackToFile("index.html"); }); }); } diff --git a/src/Components/test/testassets/TestServer/Components.TestServer.csproj b/src/Components/test/testassets/TestServer/Components.TestServer.csproj index d512e61f61c874c6c45730cc458fb744d2ec327c..dc2e60a299d06b618b5e698fc0a5384afd378b3b 100644 --- a/src/Components/test/testassets/TestServer/Components.TestServer.csproj +++ b/src/Components/test/testassets/TestServer/Components.TestServer.csproj @@ -6,9 +6,8 @@ </PropertyGroup> <ItemGroup> - <Reference Include="Microsoft.AspNetCore" /> <Reference Include="Microsoft.AspNetCore.Authentication.Cookies" /> - <Reference Include="Microsoft.AspNetCore.Blazor.Server" /> + <Reference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" /> <Reference Include="Microsoft.AspNetCore.Components.Server" /> <Reference Include="Microsoft.AspNetCore.Cors" /> <Reference Include="Microsoft.AspNetCore.Mvc" /> @@ -19,7 +18,7 @@ </ItemGroup> <ItemGroup> - <ProjectReference Include="..\..\..\Blazor\DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj" /> + <ProjectReference Include="..\..\..\WebAssembly\DevServer\src\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj" /> <ProjectReference Include="..\BasicTestApp\BasicTestApp.csproj" /> </ItemGroup> diff --git a/src/Components/test/testassets/TestServer/CorsStartup.cs b/src/Components/test/testassets/TestServer/CorsStartup.cs index 28dda32d903adfe391052229c52a607eb7c3ea1e..86740371176173ca28696d7d07dc2672f4b1f821 100644 --- a/src/Components/test/testassets/TestServer/CorsStartup.cs +++ b/src/Components/test/testassets/TestServer/CorsStartup.cs @@ -45,8 +45,8 @@ namespace TestServer // Mount the server-side Blazor app on /subdir app.Map("/subdir", app => { + app.UseBlazorFrameworkFiles(); app.UseStaticFiles(); - app.UseClientSideBlazorFiles<BasicTestApp.Program>(); app.UseRouting(); @@ -55,7 +55,7 @@ namespace TestServer app.UseEndpoints(endpoints => { endpoints.MapControllers(); - endpoints.MapFallbackToClientSideBlazor<BasicTestApp.Program>("index.html"); + endpoints.MapFallbackToFile("index.html"); }); }); } diff --git a/src/Components/test/testassets/TestServer/InternationalizationStartup.cs b/src/Components/test/testassets/TestServer/InternationalizationStartup.cs index 7521ebe34b28aee3938bad067ac2621ec99e7727..ac2fa85effa1658dd2fab98cb8cbacfce2487184 100644 --- a/src/Components/test/testassets/TestServer/InternationalizationStartup.cs +++ b/src/Components/test/testassets/TestServer/InternationalizationStartup.cs @@ -36,8 +36,8 @@ namespace TestServer // Mount the server-side Blazor app on /subdir app.Map("/subdir", app => { + app.UseBlazorFrameworkFiles(); app.UseStaticFiles(); - app.UseClientSideBlazorFiles<BasicTestApp.Program>(); app.UseRequestLocalization(options => { diff --git a/src/Components/test/testassets/TestServer/Program.cs b/src/Components/test/testassets/TestServer/Program.cs index 77f09efedc731d70e8c711b5da9961c141b0158c..b8dcc9675ec49c89d09b393a341b86f3671c362a 100644 --- a/src/Components/test/testassets/TestServer/Program.cs +++ b/src/Components/test/testassets/TestServer/Program.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; -using DevServerProgram = Microsoft.AspNetCore.Blazor.DevServer.Server.Program; +using DevServerProgram = Microsoft.AspNetCore.Components.WebAssembly.DevServer.Server.Program; namespace TestServer { @@ -23,6 +23,7 @@ namespace TestServer ["Server authentication"] = (BuildWebHost<ServerAuthenticationStartup>(CreateAdditionalArgs(args)), "/subdir"), ["CORS (WASM)"] = (BuildWebHost<CorsStartup>(CreateAdditionalArgs(args)), "/subdir"), ["Prerendering (Server-side)"] = (BuildWebHost<PrerenderedStartup>(CreateAdditionalArgs(args)), "/prerendered"), + ["Client-side with fallback"] = (BuildWebHost<StartupWithMapFallbackToClientSideBlazor>(CreateAdditionalArgs(args)), "/fallback"), ["Multiple components (Server-side)"] = (BuildWebHost<MultipleComponents>(CreateAdditionalArgs(args)), "/multiple-components"), ["Globalization + Localization (Server-side)"] = (BuildWebHost<InternationalizationStartup>(CreateAdditionalArgs(args)), "/subdir"), ["Server-side blazor"] = (BuildWebHost<ServerStartup>(CreateAdditionalArgs(args)), "/subdir"), diff --git a/src/Components/test/testassets/TestServer/StartupWithMapFallbackToClientSideBlazor.cs b/src/Components/test/testassets/TestServer/StartupWithMapFallbackToClientSideBlazor.cs index d32f48f8e3a66a1b686be18ecfc0c227744cb670..a65b87c23096e35b8ad0a1e7986eba9987bef795 100644 --- a/src/Components/test/testassets/TestServer/StartupWithMapFallbackToClientSideBlazor.cs +++ b/src/Components/test/testassets/TestServer/StartupWithMapFallbackToClientSideBlazor.cs @@ -1,11 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using BasicTestApp; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Components.Server; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -34,49 +31,43 @@ namespace TestServer } // The client-side files middleware needs to be here because the base href in hardcoded to /subdir/ - app.Map("/subdir", app => + app.Map("/subdir", subApp => { - app.UseClientSideBlazorFiles<BasicTestApp.Program>(); - }); - - // The calls to `Map` allow us to test each of these overloads, while keeping them isolated. - app.Map("/filepath", app => - { - app.UseRouting(); + subApp.UseBlazorFrameworkFiles(); + subApp.UseStaticFiles(); - app.UseEndpoints(endpoints => + // The calls to `Map` allow us to test each of these overloads, while keeping them isolated. + subApp.Map("/filepath", filepath => { - endpoints.MapFallbackToClientSideBlazor<BasicTestApp.Program>("index.html"); + filepath.UseRouting(); + filepath.UseEndpoints(endpoints => + { + endpoints.MapFallbackToFile("index.html"); + }); }); - }); - - app.Map("/pattern_filepath", app => - { - app.UseRouting(); - - app.UseEndpoints(endpoints => + subApp.Map("/pattern_filepath", patternFilePath => { - endpoints.MapFallbackToClientSideBlazor<BasicTestApp.Program>("test/{*path:nonfile}", "index.html"); + patternFilePath.UseRouting(); + patternFilePath.UseEndpoints(endpoints => + { + endpoints.MapFallbackToFile("test/{*path:nonfile}", "index.html"); + }); }); - }); - - app.Map("/assemblypath_filepath", app => - { - app.UseRouting(); - - app.UseEndpoints(endpoints => + subApp.Map("/assemblypath_filepath", assemblyPathFilePath => { - endpoints.MapFallbackToClientSideBlazor(typeof(BasicTestApp.Program).Assembly.Location, "index.html"); + assemblyPathFilePath.UseRouting(); + assemblyPathFilePath.UseEndpoints(endpoints => + { + endpoints.MapFallbackToFile("index.html"); + }); }); - }); - - app.Map("/assemblypath_pattern_filepath", app => - { - app.UseRouting(); - - app.UseEndpoints(endpoints => + subApp.Map("/assemblypath_pattern_filepath", assemblyPatternFilePath => { - endpoints.MapFallbackToClientSideBlazor(typeof(BasicTestApp.Program).Assembly.Location, "test/{*path:nonfile}", "index.html"); + assemblyPatternFilePath.UseRouting(); + assemblyPatternFilePath.UseEndpoints(endpoints => + { + endpoints.MapFallbackToFile("test/{*path:nonfile}", "index.html"); + }); }); }); } diff --git a/src/Mvc/Mvc.NewtonsoftJson/src/JsonArrayPool.cs b/src/Mvc/Mvc.NewtonsoftJson/src/JsonArrayPool.cs index c358bc2ba79483510346bd3a19d662357a42be3e..6b50b5bf846b8ca49a586c0498f0bedd5fed943a 100644 --- a/src/Mvc/Mvc.NewtonsoftJson/src/JsonArrayPool.cs +++ b/src/Mvc/Mvc.NewtonsoftJson/src/JsonArrayPool.cs @@ -37,3 +37,4 @@ namespace Microsoft.AspNetCore.Mvc.NewtonsoftJson } } } + \ No newline at end of file diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Client.csproj.in b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Client.csproj.in deleted file mode 100644 index 70927b08e331758b3feaaf68cea816fd3023bddd..0000000000000000000000000000000000000000 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Client.csproj.in +++ /dev/null @@ -1,20 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk.Web"> - - <PropertyGroup> - <TargetFramework>netstandard2.1</TargetFramework> - <RazorLangVersion>3.0</RazorLangVersion> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.Blazor" Version="${MicrosoftAspNetCoreBlazorPackageVersion}" /> - <PackageReference Include="Microsoft.AspNetCore.Blazor.Build" Version="${MicrosoftAspNetCoreBlazorBuildPackageVersion}" PrivateAssets="all" /> - <PackageReference Include="Microsoft.AspNetCore.Blazor.DevServer" Version="${MicrosoftAspNetCoreBlazorDevServerPackageVersion}" PrivateAssets="all" /> - <PackageReference Include="Microsoft.AspNetCore.Blazor.HttpClient" Version="${MicrosoftAspNetCoreBlazorHttpClientPackageVersion}" /> - </ItemGroup> - <!--#if Hosted --> - <ItemGroup> - <ProjectReference Include="..\Shared\BlazorWasm-CSharp.Shared.csproj" /> - </ItemGroup> - <!--#endif --> - -</Project> diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Server.csproj.in b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Server.csproj.in deleted file mode 100644 index 5fe7473679dce2654d7951b5cc44f25b3a478018..0000000000000000000000000000000000000000 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Server.csproj.in +++ /dev/null @@ -1,16 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk.Web"> - - <PropertyGroup> - <TargetFramework>${DefaultNetCoreTargetFramework}</TargetFramework> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.Blazor.Server" Version="${MicrosoftAspNetCoreBlazorServerPackageVersion}" /> - </ItemGroup> - - <ItemGroup> - <ProjectReference Include="..\Client\BlazorWasm-CSharp.Client.csproj" /> - <ProjectReference Include="..\Shared\BlazorWasm-CSharp.Shared.csproj" /> - </ItemGroup> - -</Project> diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/Microsoft.AspNetCore.Blazor.Templates.csproj b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/Microsoft.AspNetCore.Blazor.Templates.csproj deleted file mode 100644 index 65457a000d3566e81d13822a8495049e2c09de6b..0000000000000000000000000000000000000000 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/Microsoft.AspNetCore.Blazor.Templates.csproj +++ /dev/null @@ -1,45 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - <PropertyGroup> - <BlazorProjectsRoot>$(RepoRoot)src\Components\Blazor\</BlazorProjectsRoot> - </PropertyGroup> - - <Import Project="$(BlazorProjectsRoot)Blazor.Version.props" /> - - <PropertyGroup> - <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> - <IsShippingPackage>true</IsShippingPackage> - <Description>Templates for ASP.NET Core Blazor projects.</Description> - <PackageTags>$(PackageTags);blazor;spa</PackageTags> - </PropertyGroup> - - <PropertyGroup> - <!-- Lists the versions of dependencies not built in this repo. Packages produced from this repo should be listed as a PackageVersionVariableReference. --> - <GeneratedContentProperties> - DefaultNetCoreTargetFramework=$(DefaultNetCoreTargetFramework); - MicrosoftAspNetCoreBlazorPackageVersion=$(MicrosoftAspNetCoreBlazorPackageVersion); - MicrosoftAspNetCoreBlazorBuildPackageVersion=$(MicrosoftAspNetCoreBlazorBuildPackageVersion); - MicrosoftAspNetCoreBlazorDevServerPackageVersion=$(MicrosoftAspNetCoreBlazorDevServerPackageVersion); - MicrosoftAspNetCoreBlazorHttpClientPackageVersion=$(MicrosoftAspNetCoreBlazorHttpClientPackageVersion); - MonoWebAssemblyInteropPackageVersion=$(MonoWebAssemblyInteropPackageVersion); - MicrosoftEntityFrameworkCoreSqlServerPackageVersion=$(MicrosoftEntityFrameworkCoreSqlServerPackageVersion); - MicrosoftAspNetCoreBlazorServerPackageVersion=$(MicrosoftAspNetCoreBlazorServerPackageVersion); - </GeneratedContentProperties> - </PropertyGroup> - - <ItemGroup> - <!-- These projects product packages that the templates depend on. See GenerateContent.targets --> - <PackageVersionVariableReference Include="$(BlazorProjectsRoot)Blazor\src\Microsoft.AspNetCore.Blazor.csproj" /> - <PackageVersionVariableReference Include="$(BlazorProjectsRoot)Build\src\Microsoft.AspNetCore.Blazor.Build.csproj" /> - <PackageVersionVariableReference Include="$(BlazorProjectsRoot)DevServer\src\Microsoft.AspNetCore.Blazor.DevServer.csproj" /> - <PackageVersionVariableReference Include="$(BlazorProjectsRoot)Http\src\Microsoft.AspNetCore.Blazor.HttpClient.csproj" /> - <PackageVersionVariableReference Include="$(BlazorProjectsRoot)Mono.WebAssembly.Interop\src\Mono.WebAssembly.Interop.csproj" /> - <PackageVersionVariableReference Include="$(BlazorProjectsRoot)Server\src\Microsoft.AspNetCore.Blazor.Server.csproj" /> - </ItemGroup> - - <ItemGroup> - <GeneratedContent Include="BlazorWasm-CSharp.Client.csproj.in" OutputPath="content/BlazorWasm-CSharp/Client/BlazorWasm-CSharp.Client.csproj" /> - <GeneratedContent Include="BlazorWasm-CSharp.Shared.csproj.in" OutputPath="content/BlazorWasm-CSharp/Shared/BlazorWasm-CSharp.Shared.csproj" /> - <GeneratedContent Include="BlazorWasm-CSharp.Server.csproj.in" OutputPath="content/BlazorWasm-CSharp/Server/BlazorWasm-CSharp.Server.csproj" /> - </ItemGroup> - -</Project> diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/dotnetcli.host.json deleted file mode 100644 index 4e89e1d2dca8c123c267df4fa69cac184f00b0a8..0000000000000000000000000000000000000000 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/dotnetcli.host.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/dotnetcli.host", - "symbolInfo": { - "skipRestore": { - "longName": "no-restore", - "shortName": "" - }, - "Hosted": { - "longName": "hosted" - }, - "Framework": { - "longName": "framework" - } - } -} \ No newline at end of file diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/template.json b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/template.json deleted file mode 100644 index b6cb64fcec5f9c3052769b45021a4fd48fcde500..0000000000000000000000000000000000000000 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/template.json +++ /dev/null @@ -1,143 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/template", - "author": "Microsoft", - "classifications": [ - "Web", - "Blazor", - "WebAssembly" - ], - "name": "Blazor WebAssembly App", - "defaultName": "WebApplication", - "description": "A project template for creating a Blazor app that runs on WebAssembly and is optionally hosted by an ASP.NET Core app. This template can be used for web apps with rich dynamic user interfaces (UIs).", - "groupIdentity": "Microsoft.Web.Blazor.Wasm", - "precedence": "6001", - "guids": [ - "4C26868E-5E7C-458D-82E3-040509D0C71F", - "5990939C-7E7B-4CFA-86FF-44CA5756498A", - "650B3CE7-2E93-4CC4-9F46-466686815EAA", - "0AFFA7FD-4E37-4636-AB91-3753E746DB98" - ], - "identity": "Microsoft.Web.Blazor.Wasm.CSharp", - "preferNameDirectory": true, - "primaryOutputs": [ - { - "condition": "(Hosted && (HostIdentifier == \"dotnetcli\" || HostIdentifier == \"dotnetcli-preview\"))", - "path": "BlazorWasm-CSharp.sln" - }, - { - "condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")", - "path": "Server/BlazorWasm-CSharp.Server.csproj" - }, - { - "condition": "(!Hosted)", - "path": "BlazorWasm-CSharp.Client.csproj" - }, - { - "condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")", - "path": "Client/BlazorWasm-CSharp.Client.csproj" - }, - { - "condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")", - "path": "Shared/BlazorWasm-CSharp.Shared.csproj" - } - ], - "shortName": "blazorwasm", - "sourceName": "BlazorWasm-CSharp", - "sources": [ - { - "source": "./", - "target": "./", - "exclude": [ - ".template.config/**" - ], - "modifiers": [ - { - "condition": "(!Hosted)", - "exclude": [ - "Server/**", - "Shared/**", - "*.sln" - ], - "rename": { - ".Client.csproj": ".csproj", - "Client": "." - } - }, - { - "condition": "(Hosted)", - "exclude": [ - "Client/wwwroot/sample-data/**" - ] - }, - { - "condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")", - "exclude": [ - "*.sln" - ] - } - ] - } - ], - "symbols": { - "Framework": { - "type": "parameter", - "description": "The target framework for the project.", - "datatype": "choice", - "choices": [ - { - "choice": "netcoreapp3.1", - "description": "Target netcoreapp3.1" - } - ], - "replaces": "netcoreapp3.1", - "defaultValue": "netcoreapp3.1" - }, - "HostIdentifier": { - "type": "bind", - "binding": "HostIdentifier" - }, - "skipRestore": { - "type": "parameter", - "datatype": "bool", - "description": "If specified, skips the automatic restore of the project on create.", - "defaultValue": "false" - }, - "Hosted": { - "type": "parameter", - "datatype": "bool", - "defaultValue": "false", - "description": "If specified, includes an ASP.NET Core host for the Blazor app." - } - }, - "tags": { - "language": "C#", - "type": "project" - }, - "postActions": [ - { - "condition": "(!skipRestore && Hosted)", - "description": "Restore NuGet packages required by this project.", - "manualInstructions": [ - { - "text": "Run 'dotnet restore'" - } - ], - "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", - "continueOnError": true - }, - { - "condition": "(!skipRestore && !Hosted)", - "description": "Restore NuGet packages required by this project.", - "manualInstructions": [ - { - "text": "Run 'dotnet restore'" - } - ], - "args": { - "files": ["BlazorWasm-CSharp.Client.csproj"] - }, - "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", - "continueOnError": true - } - ] -} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/App.razor b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/App.razor deleted file mode 100644 index 1c360b7121a718569da3463c4b8409ea34ffa50a..0000000000000000000000000000000000000000 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/App.razor +++ /dev/null @@ -1,10 +0,0 @@ -<Router AppAssembly="@typeof(Program).Assembly"> - <Found Context="routeData"> - <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> - </Found> - <NotFound> - <LayoutView Layout="@typeof(MainLayout)"> - <p>Sorry, there's nothing at this address.</p> - </LayoutView> - </NotFound> -</Router> diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Pages/Index.razor b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Pages/Index.razor deleted file mode 100644 index e54d914390e1d84037c618ed12048b7915a8beeb..0000000000000000000000000000000000000000 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Pages/Index.razor +++ /dev/null @@ -1,7 +0,0 @@ -@page "/" - -<h1>Hello, world!</h1> - -Welcome to your new app. - -<SurveyPrompt Title="How is Blazor working for you?" /> diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Program.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Program.cs deleted file mode 100644 index 5790d212dcdecb2088458eda260124aee8baeb7c..0000000000000000000000000000000000000000 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Program.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Text; -using Microsoft.AspNetCore.Blazor.Hosting; -using Microsoft.Extensions.DependencyInjection; - -#if (Hosted) -namespace BlazorWasm_CSharp.Client -#else -namespace BlazorWasm_CSharp -#endif -{ - public class Program - { - public static async Task Main(string[] args) - { - var builder = WebAssemblyHostBuilder.CreateDefault(args); - builder.RootComponents.Add<App>("app"); - - await builder.Build().RunAsync(); - } - } -} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Properties/launchSettings.json b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Properties/launchSettings.json deleted file mode 100644 index 5b741f29135a86b0930deb9ee1ca39aeb24ab042..0000000000000000000000000000000000000000 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Properties/launchSettings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "useWebAssemblyDebugging": true -} \ No newline at end of file diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/_Imports.razor b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/_Imports.razor deleted file mode 100644 index 912a5070c2f43aca1bac8277765e8edf9553007e..0000000000000000000000000000000000000000 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/_Imports.razor +++ /dev/null @@ -1,12 +0,0 @@ -@using System.Net.Http -@using Microsoft.AspNetCore.Components.Forms -@using Microsoft.AspNetCore.Components.Routing -@using Microsoft.AspNetCore.Components.Web -@using Microsoft.JSInterop -@*#if (!Hosted) -@using BlazorWasm_CSharp -@using BlazorWasm_CSharp.Shared -#else -@using BlazorWasm_CSharp.Client -@using BlazorWasm_CSharp.Client.Shared -#endif*@ diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Program.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Program.cs deleted file mode 100644 index b969665b212b5ea1014e388548f0f6120aa9fe46..0000000000000000000000000000000000000000 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Program.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; - -namespace BlazorWasm_CSharp.Server -{ - public class Program - { - public static void Main(string[] args) - { - BuildWebHost(args).Run(); - } - - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseConfiguration(new ConfigurationBuilder() - .AddCommandLine(args) - .Build()) - .UseStartup<Startup>() - .Build(); - } -} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Startup.cs b/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Startup.cs deleted file mode 100644 index ac46fb46ddb3fb0c0f89490708278186cabccba3..0000000000000000000000000000000000000000 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Startup.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.ResponseCompression; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System.Linq; - -namespace BlazorWasm_CSharp.Server -{ - public class Startup - { - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - services.AddMvc(); - services.AddResponseCompression(opts => - { - opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat( - new[] { "application/octet-stream" }); - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.UseResponseCompression(); - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseBlazorDebugging(); - } - - app.UseStaticFiles(); - app.UseClientSideBlazorFiles<Client.Program>(); - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute(); - endpoints.MapFallbackToClientSideBlazor<Client.Program>("index.html"); - }); - } - } -} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/.gitignore b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/.gitignore similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/.gitignore rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/.gitignore diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/ComponentsWebAssembly-CSharp.Client.csproj.in b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/ComponentsWebAssembly-CSharp.Client.csproj.in new file mode 100644 index 0000000000000000000000000000000000000000..4b856535b24c88ba3bf16d0668feedcb129dc9ee --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/ComponentsWebAssembly-CSharp.Client.csproj.in @@ -0,0 +1,36 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + + <PropertyGroup> + <TargetFramework>netstandard2.1</TargetFramework> + <RazorLangVersion>3.0</RazorLangVersion> + <!--#if PWA --> + <ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest> + <!--#endif --> + <!--#if Hosted --> + <AssemblyName Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">`$(AssemblyName.Replace(' ', '_'))</AssemblyName> + <!--#endif --> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="${MicrosoftAspNetCoreComponentsWebAssemblyPackageVersion}" /> + <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="${MicrosoftAspNetCoreComponentsWebAssemblyBuildPackageVersion}" PrivateAssets="all" /> + <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="${MicrosoftAspNetCoreComponentsWebAssemblyDevServerPackageVersion}" PrivateAssets="all" /> + <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="${MicrosoftAspNetCoreComponentsWebAssemblyAuthenticationPackageVersion}" Condition="'$(IndividualLocalAuth)' == 'true'" /> + <PackageReference Include="Microsoft.Authentication.WebAssembly.Msal" Version="${MicrosoftAuthenticationWebAssemblyMsalPackageVersion}" Condition="'$(OrganizationalAuth)' == 'true' OR '$(IndividualB2CAuth)' == 'true'" /> + <PackageReference Include="Microsoft.Extensions.Http" Version="${MicrosoftExtensionsHttpPackageVersion}" Condition="'$(NoAuth)' != 'true' AND '$(Hosted)' == 'true'" /> + <PackageReference Include="System.Net.Http.Json" Version="${SystemNetHttpJsonPackageVersion}" /> + </ItemGroup> + + <!--#if Hosted --> + <ItemGroup> + <ProjectReference Include="..\Shared\ComponentsWebAssembly-CSharp.Shared.csproj" /> + </ItemGroup> + + <!--#endif --> + <!--#if PWA --> + <ItemGroup> + <ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" /> + </ItemGroup> + + <!--#endif --> +</Project> diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/ComponentsWebAssembly-CSharp.Server.csproj.in b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/ComponentsWebAssembly-CSharp.Server.csproj.in new file mode 100644 index 0000000000000000000000000000000000000000..1c350c70aae5a8b9489c896eff171d5b9652b61f --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/ComponentsWebAssembly-CSharp.Server.csproj.in @@ -0,0 +1,46 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + + <PropertyGroup> + <TargetFramework>${DefaultNetCoreTargetFramework}</TargetFramework> + <UserSecretsId Condition="'$(IndividualAuth)' == 'True' OR '$(OrganizationalAuth)' == 'True'">ComponentsWebAssembly-CSharp.Server-53bc9b9d-9d6a-45d4-8429-2a2761773502</UserSecretsId> + <WebProject_DirectoryAccessLevelKey Condition="'$(OrganizationalAuth)' == 'True' AND '$(OrgReadAccess)' != 'True'">0</WebProject_DirectoryAccessLevelKey> + <WebProject_DirectoryAccessLevelKey Condition="'$(OrganizationalAuth)' == 'True' AND '$(OrgReadAccess)' == 'True'">1</WebProject_DirectoryAccessLevelKey> + <NoDefaultLaunchSettingsFile Condition="'$(ExcludeLaunchSettings)' == 'True'">True</NoDefaultLaunchSettingsFile> + <RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">ComponentsWebAssembly-CSharp.Server</RootNamespace> + <AssemblyName Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">`$(AssemblyName.Replace(' ', '_'))</AssemblyName> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="${MicrosoftAspNetCoreComponentsWebAssemblyServerPackageVersion}" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Client\ComponentsWebAssembly-CSharp.Client.csproj" /> + <ProjectReference Include="..\Shared\ComponentsWebAssembly-CSharp.Shared.csproj" /> + </ItemGroup> + + <!--#if (IndividualLocalAuth && !UseLocalDB) --> + <ItemGroup> + <None Update="app.db" CopyToOutputDirectory="PreserveNewest" ExcludeFromSingleFile="true" /> + </ItemGroup> + + <!--#endif --> + <!--#if (IndividualLocalAuth) --> + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="${MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion}" /> + <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="${MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion}" /> + <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="${MicrosoftAspNetCoreIdentityUIPackageVersion}" /> + <PackageReference Include="Microsoft.AspNetCore.ApiAuthorization.IdentityServer" Version="${MicrosoftAspNetCoreApiAuthorizationIdentityServerPackageVersion}" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="${MicrosoftEntityFrameworkCoreSqlServerPackageVersion}" Condition="'$(UseLocalDB)' == 'True'" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="${MicrosoftEntityFrameworkCoreSqlitePackageVersion}" Condition="'$(UseLocalDB)' != 'True'" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="${MicrosoftEntityFrameworkCoreToolsPackageVersion}" /> + </ItemGroup> + <!--#endif --> + <!--#if (OrganizationalAuth || IndividualB2CAuth) --> + <ItemGroup> + <PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="${MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion}" Condition="'$(OrganizationalAuth)' == 'True'" /> + <PackageReference Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" Version="${MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion}" Condition="'$(IndividualB2CAuth)' == 'True'" /> + </ItemGroup> + <!--#endif --> + +</Project> diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Shared.csproj.in b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/ComponentsWebAssembly-CSharp.Shared.csproj.in similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/BlazorWasm-CSharp.Shared.csproj.in rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/ComponentsWebAssembly-CSharp.Shared.csproj.in diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/Microsoft.AspNetCore.Components.WebAssembly.Templates.csproj b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/Microsoft.AspNetCore.Components.WebAssembly.Templates.csproj new file mode 100644 index 0000000000000000000000000000000000000000..18d8c0fa2307ed7528e8818133ec1cc6faf6adbb --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/Microsoft.AspNetCore.Components.WebAssembly.Templates.csproj @@ -0,0 +1,53 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <ComponentsWebAssemblyProjectsRoot>$(RepoRoot)src\Components\WebAssembly\</ComponentsWebAssemblyProjectsRoot> + </PropertyGroup> + + <PropertyGroup> + <TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework> + <IsShippingPackage>true</IsShippingPackage> + <Description>Templates for ASP.NET Core Blazor WebAssembly projects.</Description> + <PackageTags>$(PackageTags);blazor;spa</PackageTags> + </PropertyGroup> + + <PropertyGroup> + <!-- Lists the versions of dependencies not built in this repo. Packages produced from this repo should be listed as a PackageVersionVariableReference. --> + <GeneratedContentProperties> + DefaultNetCoreTargetFramework=$(DefaultNetCoreTargetFramework); + MicrosoftAspNetCoreComponentsWebAssemblyPackageVersion=$(MicrosoftAspNetCoreComponentsWebAssemblyPackageVersion); + MicrosoftAspNetCoreComponentsWebAssemblyBuildPackageVersion=$(MicrosoftAspNetCoreComponentsWebAssemblyBuildPackageVersion); + MicrosoftAspNetCoreComponentsWebAssemblyDevServerPackageVersion=$(MicrosoftAspNetCoreComponentsWebAssemblyDevServerPackageVersion); + MicrosoftAspNetCoreBlazorHttpClientPackageVersion=$(MicrosoftAspNetCoreBlazorHttpClientPackageVersion); + MicrosoftAspNetCoreComponentsWebAssemblyServerPackageVersion=$(MicrosoftAspNetCoreComponentsWebAssemblyServerPackageVersion); + MicrosoftAspNetCoreComponentsAuthorizationPackageVersion=$(MicrosoftAspNetCoreComponentsAuthorizationPackageVersion); + MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion=$(MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion); + MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion=$(MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion); + MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion=$(MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion); + MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion=$(MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion); + MicrosoftAspNetCoreIdentityUIPackageVersion=$(MicrosoftAspNetCoreIdentityUIPackageVersion); + MicrosoftAspNetCoreApiAuthorizationIdentityServerPackageVersion=$(MicrosoftAspNetCoreApiAuthorizationIdentityServerPackageVersion); + MicrosoftEntityFrameworkCoreSqlServerPackageVersion=$(MicrosoftEntityFrameworkCoreSqlServerPackageVersion); + MicrosoftEntityFrameworkCoreSqlitePackageVersion=$(MicrosoftEntityFrameworkCoreSqlitePackageVersion); + MicrosoftEntityFrameworkCoreToolsPackageVersion=$(MicrosoftEntityFrameworkCoreToolsPackageVersion); + MicrosoftExtensionsHttpPackageVersion=$(MicrosoftExtensionsHttpPackageVersion); + SystemNetHttpJsonPackageVersion=$(SystemNetHttpJsonPackageVersion) + </GeneratedContentProperties> + </PropertyGroup> + + <ItemGroup> + <!-- These projects product packages that the templates depend on. See GenerateContent.targets --> + <PackageVersionVariableReference Include="$(ComponentsWebAssemblyProjectsRoot)WebAssembly\src\Microsoft.AspNetCore.Components.WebAssembly.csproj" /> + <PackageVersionVariableReference Include="$(ComponentsWebAssemblyProjectsRoot)Build\src\Microsoft.AspNetCore.Components.WebAssembly.Build.csproj" /> + <PackageVersionVariableReference Include="$(ComponentsWebAssemblyProjectsRoot)DevServer\src\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj" /> + <PackageVersionVariableReference Include="$(ComponentsWebAssemblyProjectsRoot)Server\src\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj" /> + <PackageVersionVariableReference Include="$(ComponentsWebAssemblyProjectsRoot)WebAssembly.Authentication\src\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj" /> + <PackageVersionVariableReference Include="$(ComponentsWebAssemblyProjectsRoot)Authentication.Msal\src\Microsoft.Authentication.WebAssembly.Msal.csproj" /> + </ItemGroup> + + <ItemGroup> + <GeneratedContent Include="ComponentsWebAssembly-CSharp.Client.csproj.in" OutputPath="content/ComponentsWebAssembly-CSharp/Client/ComponentsWebAssembly-CSharp.Client.csproj" /> + <GeneratedContent Include="ComponentsWebAssembly-CSharp.Shared.csproj.in" OutputPath="content/ComponentsWebAssembly-CSharp/Shared/ComponentsWebAssembly-CSharp.Shared.csproj" /> + <GeneratedContent Include="ComponentsWebAssembly-CSharp.Server.csproj.in" OutputPath="content/ComponentsWebAssembly-CSharp/Server/ComponentsWebAssembly-CSharp.Server.csproj" /> + </ItemGroup> + +</Project> diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/dotnetcli.host.json b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/dotnetcli.host.json new file mode 100644 index 0000000000000000000000000000000000000000..05caada2d06be5855eee03ed23a62c4d437eaaa6 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/dotnetcli.host.json @@ -0,0 +1,82 @@ +{ + "$schema": "http://json.schemastore.org/dotnetcli.host", + "symbolInfo": { + "skipRestore": { + "longName": "no-restore", + "shortName": "" + }, + "Hosted": { + "longName": "hosted" + }, + "PWA": { + "longName": "pwa" + }, + "Framework": { + "longName": "framework" + }, + "UseLocalDB": { + "longName": "use-local-db" + }, + "AADInstance": { + "longName": "aad-instance", + "shortName": "" + }, + "AAdB2CInstance": { + "longName": "aad-b2c-instance", + "shortName": "" + }, + "SignUpSignInPolicyId": { + "longName": "susi-policy-id", + "shortName": "ssp" + }, + "OrgReadAccess": { + "longName": "org-read-access", + "shortName": "r" + }, + "ClientId": { + "longName": "client-id", + "shortName": "" + }, + "AppIDUri": { + "longName": "app-id-uri", + "shortName": "" + }, + "APIClientId": { + "longName": "api-client-id", + "shortName": "" + }, + "Domain": { + "longName": "domain", + "shortName": "" + }, + "TenantId": { + "longName": "tenant-id", + "shortName": "" + }, + "DefaultScope": { + "longName": "default-scope", + "shortName": "s" + }, + "Authority": { + "longName": "authority", + "shortName": "" + }, + "HttpPort": { + "isHidden": true + }, + "HttpsPort": { + "isHidden": true + }, + "ExcludeLaunchSettings": { + "longName": "exclude-launch-settings", + "shortName": "" + }, + "UserSecretsId": { + "isHidden": true + }, + "NoHttps": { + "longName": "no-https", + "shortName": "" + } + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/icon.png b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/icon.png similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/icon.png rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/icon.png diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json new file mode 100644 index 0000000000000000000000000000000000000000..81c72edc355c36b5a9413fe4c4c3752cca83967f --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/template.json @@ -0,0 +1,487 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "Microsoft", + "classifications": [ + "Web", + "Blazor", + "WebAssembly" + ], + "name": "Blazor WebAssembly App", + "defaultName": "WebApplication", + "description": "A project template for creating a Blazor app that runs on WebAssembly and is optionally hosted by an ASP.NET Core app. This template can be used for web apps with rich dynamic user interfaces (UIs).", + "groupIdentity": "Microsoft.Web.Blazor.Wasm", + "precedence": "6001", + "guids": [ + "4C26868E-5E7C-458D-82E3-040509D0C71F", + "5990939C-7E7B-4CFA-86FF-44CA5756498A", + "650B3CE7-2E93-4CC4-9F46-466686815EAA", + "0AFFA7FD-4E37-4636-AB91-3753E746DB98", + "09732173-2cef-46b7-83db-1334bcb079d3", // Tenant ID + "53bc9b9d-9d6a-45d4-8429-2a2761773502" // Client ID + ], + "identity": "Microsoft.Web.Blazor.Wasm.CSharp", + "preferNameDirectory": true, + "primaryOutputs": [ + { + "condition": "(Hosted && (HostIdentifier == \"dotnetcli\" || HostIdentifier == \"dotnetcli-preview\"))", + "path": "ComponentsWebAssembly-CSharp.sln" + }, + { + "condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")", + "path": "Server/ComponentsWebAssembly-CSharp.Server.csproj" + }, + { + "condition": "(!Hosted)", + "path": "ComponentsWebAssembly-CSharp.Client.csproj" + }, + { + "condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")", + "path": "Client/ComponentsWebAssembly-CSharp.Client.csproj" + }, + { + "condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")", + "path": "Shared/ComponentsWebAssembly-CSharp.Shared.csproj" + } + ], + "shortName": "blazorwasm", + "sourceName": "ComponentsWebAssembly-CSharp", + "sources": [ + { + "source": "./", + "target": "./", + "exclude": [ + ".template.config/**" + ], + "modifiers": [ + { + "condition": "(!Hosted)", + "exclude": [ + "Server/**", + "Shared/**", + "*.sln" + ], + "rename": { + ".Client.csproj": ".csproj", + "Client": "." + } + }, + { + "condition": "(Hosted)", + "exclude": [ + "Client/wwwroot/sample-data/**" + ] + }, + { + "condition": "(Hosted && HostIdentifier != \"dotnetcli\" && HostIdentifier != \"dotnetcli-preview\")", + "exclude": [ + "*.sln" + ] + }, + { + "condition": "(!PWA)", + "exclude": [ + "Client/wwwroot/service-worker*.js", + "Client/wwwroot/manifest.json", + "Client/wwwroot/icon-512.png" + ] + }, + { + "condition": "(!IndividualLocalAuth || UseLocalDB)", + "exclude": [ + "Server/app.db" + ] + }, + { + "condition": "(!IndividualLocalAuth)", + "exclude": [ + "Server/Data/SqlLite/**", + "Server/Data/SqlServer/**", + "Server/Data/ApplicationDbContext.cs", + "Server/Areas/**", + "Client/wwwroot/appsettings.Development.json" + ] + }, + { + "condition": "(IndividualLocalAuth && UseLocalDB)", + "rename": { + "Server/Data/SqlServer/": "Server/Data/Migrations/" + }, + "exclude": [ + "Server/Data/SqlLite/**" + ] + }, + { + "condition": "(IndividualLocalAuth && !UseLocalDB)", + "rename": { + "Server/Data/SqlLite/": "Server/Data/Migrations/" + }, + "exclude": [ + "Server/Data/SqlServer/**" + ] + }, + { + "condition": "(Hosted && NoAuth)", + "rename": { + "Client/Shared/MainLayout.NoAuth.razor": "Client/Shared/MainLayout.razor" + }, + "exclude": [ + "Client/Pages/Authentication.razor", + "Client/Shared/LoginDisplay.*.razor", + "Client/Shared/MainLayout.Auth.razor", + "Client/Shared/RedirectToLogin.razor", + "Client/wwwroot/appsettings.Development.json", + "Client/wwwroot/appsettings.json" + ] + }, + { + "condition": "(Hosted && !NoAuth)", + "rename": { + "Client/Shared/MainLayout.Auth.razor": "Client/Shared/MainLayout.razor" + }, + "exclude": [ + "Client/Shared/MainLayout.NoAuth.razor" + ] + }, + { + "condition": "(Hosted && IndividualLocalAuth)", + "rename": { + "Client/Shared/LoginDisplay.IndividualLocalAuth.razor": "Client/Shared/LoginDisplay.razor" + }, + "exclude": [ + "Client/Shared/LoginDisplay.IndividualMsalAuth.razor", + "Client/wwwroot/appsettings.json", + "Client/wwwroot/appsettings.Development.json" + ] + }, + { + "condition": "(Hosted && (IndividualB2CAuth || OrganizationalAuth))", + "rename": { + "Client/Shared/LoginDisplay.IndividualMsalAuth.razor": "Client/Shared/LoginDisplay.razor" + }, + "exclude": [ + "Client/Shared/LoginDisplay.IndividualLocalAuth.razor" + ] + }, + { + "condition": "(!Hosted && NoAuth)", + "rename": { + "Client/Shared/MainLayout.NoAuth.razor": "Shared/MainLayout.razor" + }, + "exclude": [ + "Client/Pages/Authentication.razor", + "Client/Shared/LoginDisplay.*.razor", + "Client/Shared/MainLayout.Auth.razor", + "Client/Shared/RedirectToLogin.razor", + "Client/wwwroot/appsettings.Development.json", + "Client/wwwroot/appsettings.json" + ] + }, + { + "condition": "(!Hosted && !NoAuth)", + "rename": { + "Client/Shared/MainLayout.Auth.razor": "Shared/MainLayout.razor" + }, + "exclude": [ + "Client/Shared/MainLayout.NoAuth.razor" + ] + }, + { + "condition": "(!Hosted && IndividualLocalAuth)", + "rename": { + "Client/Shared/LoginDisplay.IndividualLocalAuth.razor": "Shared/LoginDisplay.razor" + }, + "exclude": [ + "Client/Shared/LoginDisplay.IndividualMsalAuth.razor" + ] + }, + { + "condition": "(!Hosted && (IndividualB2CAuth || OrganizationalAuth))", + "rename": { + "Client/Shared/LoginDisplay.IndividualMsalAuth.razor": "Shared/LoginDisplay.razor" + }, + "exclude": [ + "Client/Shared/LoginDisplay.IndividualLocalAuth.razor" + ] + }, + { + "condition": "(!IndividualLocalAuth || !Hosted)", + "exclude": [ + "Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml", + "Server/Controllers/OidcConfigurationController.cs", + "Server/Models/ApplicationUser.cs" + ] + } + ] + } + ], + "symbols": { + "Framework": { + "type": "parameter", + "description": "The target framework for the project.", + "datatype": "choice", + "choices": [ + { + "choice": "netcoreapp3.1", + "description": "Target netcoreapp3.1" + } + ], + "replaces": "netcoreapp3.1", + "defaultValue": "netcoreapp3.1" + }, + "HostIdentifier": { + "type": "bind", + "binding": "HostIdentifier" + }, + "skipRestore": { + "type": "parameter", + "datatype": "bool", + "description": "If specified, skips the automatic restore of the project on create.", + "defaultValue": "false" + }, + "Hosted": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "description": "If specified, includes an ASP.NET Core host for the Blazor WebAssembly app." + }, + "auth": { + "type": "parameter", + "datatype": "choice", + "choices": [ + { + "choice": "None", + "description": "No authentication" + }, + { + "choice": "Individual", + "description": "Individual authentication" + }, + { + "choice": "IndividualB2C", + "description": "Individual authentication with Azure AD B2C" + }, + { + "choice": "SingleOrg", + "description": "Organizational authentication for a single tenant" + } + ], + "defaultValue": "None", + "description": "The type of authentication to use" + }, + "Authority": { + "type": "parameter", + "datatype": "string", + "replaces": "https://login.microsoftonline.com/", + "description": "The authority of the OIDC provider (use with standalone Individual auth)." + }, + "MissingAuthority": { + "type": "computed", + "value": "(IndividualAuth && !Hosted && Authority == \"https://login.microsoftonline.com/\" && ClientId == \"33333333-3333-3333-33333333333333333\")" + }, + "AAdB2CInstance": { + "type": "parameter", + "datatype": "string", + "replaces": "https:////aadB2CInstance.b2clogin.com/", + "description": "The Azure Active Directory B2C instance to connect to (use with IndividualB2C auth)." + }, + "SignUpSignInPolicyId": { + "type": "parameter", + "datatype": "string", + "defaultValue": "", + "replaces": "MySignUpSignInPolicyId", + "description": "The sign-in and sign-up policy ID for this project (use with IndividualB2C auth)." + }, + "AADInstance": { + "type": "parameter", + "datatype": "string", + "defaultValue": "https://login.microsoftonline.com/", + "replaces": "https:////login.microsoftonline.com/", + "description": "The Azure Active Directory instance to connect to (use with SingleOrg)." + }, + "ClientId": { + "type": "parameter", + "datatype": "string", + "replaces": "33333333-3333-3333-33333333333333333", + "description": "The Client ID for this project (use with IndividualB2C, SingleOrg or Individual auth in standalone scenarios)." + }, + "Domain": { + "type": "parameter", + "datatype": "string", + "replaces": "qualified.domain.name", + "description": "The domain for the directory tenant (use with SingleOrg or IndividualB2C auth)." + }, + "AppIDUri": { + "type": "parameter", + "datatype": "string", + "replaces": "api.id.uri", + "description": "The App ID Uri for the server API we want to call (use with SingleOrg or IndividualB2C auth)." + }, + "APIClientId": { + "type": "parameter", + "datatype": "string", + "replaces": "11111111-1111-1111-11111111111111111", + "description": "The Client ID for the API that the server hosts (use with IndividualB2C, SingleOrg)." + }, + "DefaultScope": { + "type": "parameter", + "datatype": "string", + "replaces": "api-scope", + "defaultValue": "user_impersonation", + "description": "The API scope the client needs to request to provision an access token. (use with IndividualB2C, SingleOrg)." + }, + "TenantId": { + "type": "parameter", + "datatype": "string", + "replaces": "22222222-2222-2222-2222-222222222222", + "description": "The TenantId ID of the directory to connect to (use with SingleOrg auth)." + }, + "OrgReadAccess": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "description": "Whether or not to allow this application read access to the directory (only applies to SingleOrg)." + }, + "UserSecretsId": { + "type": "parameter", + "datatype": "string", + "replaces": "aspnet-BlazorServerWeb-CSharp-53bc9b9d-9d6a-45d4-8429-2a2761773502", + "defaultValue": "aspnet-BlazorServerWeb-CSharp-53bc9b9d-9d6a-45d4-8429-2a2761773502", + "description": "The ID to use for secrets (use with OrgReadAccess or Individual auth)." + }, + "ExcludeLaunchSettings": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "description": "Whether to exclude launchSettings.json from the generated template." + }, + "HttpPort": { + "type": "parameter", + "datatype": "integer", + "description": "Port number to use for the HTTP endpoint in launchSettings.json." + }, + "HttpPortGenerated": { + "type": "generated", + "generator": "port" + }, + "HttpPortReplacer": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "HttpPort", + "fallbackVariableName": "HttpPortGenerated" + }, + "replaces": "8080" + }, + "HttpsPort": { + "type": "parameter", + "datatype": "integer", + "description": "Port number to use for the HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if either IndividualAuth or OrganizationalAuth is used)." + }, + "HttpsPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 44300, + "high": 44399 + } + }, + "HttpsPortReplacer": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "HttpsPort", + "fallbackVariableName": "HttpsPortGenerated" + }, + "replaces": "44300" + }, + "PWA": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "description": "If specified, produces a Progressive Web Application (PWA) supporting installation and offline use." + }, + "OrganizationalAuth": { + "type": "computed", + "value": "(auth == \"SingleOrg\" || auth == \"MultiOrg\")" + }, + "MultiOrgAuth": { + "type": "computed", + "value": "(auth == \"MultiOrg\")" + }, + "SingleOrgAuth": { + "type": "computed", + "value": "(auth == \"SingleOrg\")" + }, + "IndividualLocalAuth": { + "type": "computed", + "value": "(auth == \"Individual\")" + }, + "IndividualAuth": { + "type": "computed", + "value": "(auth == \"Individual\" || auth == \"IndividualB2C\")" + }, + "IndividualB2CAuth": { + "type": "computed", + "value": "(auth == \"IndividualB2C\")" + }, + "NoAuth": { + "type": "computed", + "value": "(!(IndividualAuth || OrganizationalAuth))" + }, + "RequiresHttps": { + "type": "computed", + "value": "(OrganizationalAuth || IndividualAuth || !NoHttps)" + }, + "NoHttps": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "description": "Whether to turn off HTTPS. This option only applies if Individual, IndividualB2C, SingleOrg, or MultiOrg aren't used for --auth." + }, + "UseLocalDB": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "description": "Whether to use LocalDB instead of SQLite. This option only applies if --auth Individual or --auth IndividualB2C is specified." + }, + "copyrightYear": { + "type": "generated", + "generator": "now", + "replaces": "copyrightYear", + "parameters": { + "format": "yyyy" + } + } + }, + "tags": { + "language": "C#", + "type": "project" + }, + "postActions": [ + { + "condition": "(!skipRestore && Hosted)", + "description": "Restore NuGet packages required by this project.", + "manualInstructions": [ + { + "text": "Run 'dotnet restore'" + } + ], + "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", + "continueOnError": true + }, + { + "condition": "(!skipRestore && !Hosted)", + "description": "Restore NuGet packages required by this project.", + "manualInstructions": [ + { + "text": "Run 'dotnet restore'" + } + ], + "args": { + "files": ["ComponentsWebAssembly-CSharp.Client.csproj"] + }, + "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", + "continueOnError": true + } + ] +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/vs-2017.3.host.json b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/vs-2017.3.host.json similarity index 56% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/vs-2017.3.host.json rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/vs-2017.3.host.json index 5cb50d10a519f7fa886acab48452f5d78295dd2c..114a96d67752cdb278a0fbb1bc5595ce7c9bd52a 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/.template.config/vs-2017.3.host.json +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/.template.config/vs-2017.3.host.json @@ -18,6 +18,30 @@ "additionalWizardParameters": { "$isMultiProjectTemplate$": "true" }, + "supportedAuthentications": [ + { + "auth": "None", + "authenticationType": "NoAuth", + "allowUnsecured": true + }, + { + "auth": "Individual", + "authenticationType": "IndividualAuth", + "b2cAuthenticationOptions": "Local" + } + ], + "ports": [ + { + "name": "HttpPort", + "useHttps": false + }, + { + "name": "HttpsPort", + "useHttps": true + } + ], + "excludeLaunchSettings": false, + "disableHttpsSymbol": "NoHttps", "symbolInfo": [ { "id": "Hosted", @@ -25,6 +49,13 @@ "text": "ASP.NET Core _hosted" }, "isVisible": "true" + }, + { + "id": "PWA", + "name": { + "text": "_Progressive Web Application" + }, + "isVisible": "true" } ] } diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/App.razor b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/App.razor new file mode 100644 index 0000000000000000000000000000000000000000..48da6e96c988982917ff52ac06c18ff5228a29b8 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/App.razor @@ -0,0 +1,36 @@ +@*#if (NoAuth) +<Router AppAssembly="@typeof(Program).Assembly"> + <Found Context="routeData"> + <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> + </Found> + <NotFound> + <LayoutView Layout="@typeof(MainLayout)"> + <p>Sorry, there's nothing at this address.</p> + </LayoutView> + </NotFound> +</Router> +#else +<CascadingAuthenticationState> + <Router AppAssembly="@typeof(Program).Assembly"> + <Found Context="routeData"> + <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"> + <NotAuthorized> + @if (!context.User.Identity.IsAuthenticated) + { + <RedirectToLogin /> + } + else + { + <p>You are not authorized to access this resource.</p> + } + </NotAuthorized> + </AuthorizeRouteView> + </Found> + <NotFound> + <LayoutView Layout="@typeof(MainLayout)"> + <p>Sorry, there's nothing at this address.</p> + </LayoutView> + </NotFound> + </Router> +</CascadingAuthenticationState> +#endif*@ diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Pages/Authentication.razor b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Pages/Authentication.razor new file mode 100644 index 0000000000000000000000000000000000000000..ec9aa2f86543d4f67fb859fec8503a598509dd96 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Pages/Authentication.razor @@ -0,0 +1,7 @@ +@page "/authentication/{action}" +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +<RemoteAuthenticatorView Action="@Action" /> + +@code{ + [Parameter] public string Action { get; set; } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Pages/Counter.razor b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Pages/Counter.razor similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Pages/Counter.razor rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Pages/Counter.razor diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Pages/FetchData.razor b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Pages/FetchData.razor similarity index 63% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Pages/FetchData.razor rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Pages/FetchData.razor index 0faf18fefd348dd6bb301685e59d22d77cd5bcc0..fbf442fff84c37083dc633bcfa47b2faf20fd12b 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Pages/FetchData.razor +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Pages/FetchData.razor @@ -1,6 +1,13 @@ -@page "/fetchdata" +@page "/fetchdata" +@*#if (!NoAuth && Hosted) +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +#endif*@ @*#if (Hosted) -@using BlazorWasm_CSharp.Shared +@using ComponentsWebAssembly_CSharp.Shared +#endif*@ +@*#if (!NoAuth && Hosted) +@attribute [Authorize] #endif*@ @inject HttpClient Http @@ -43,9 +50,20 @@ else protected override async Task OnInitializedAsync() { @*#if (Hosted) - forecasts = await Http.GetJsonAsync<WeatherForecast[]>("WeatherForecast"); + @*#if (!NoAuth) + try + { + forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast"); + } + catch (AccessTokenNotAvailableException exception) + { + exception.Redirect(); + } + #else + forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast"); + #endif*@ #else - forecasts = await Http.GetJsonAsync<WeatherForecast[]>("sample-data/weather.json"); + forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json"); #endif*@ } diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Pages/Index.razor b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Pages/Index.razor new file mode 100644 index 0000000000000000000000000000000000000000..e4f568b97af47780220bc7960a42f7764202e189 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Pages/Index.razor @@ -0,0 +1,13 @@ +@page "/" + +<h1>Hello, world!</h1> + +@*#if (MissingAuthority) +<div class="alert alert-warning" role="alert"> + Before authentication will function correctly, you must configure your provider details in <code>Program.cs</code> +</div> + +#endif*@ +Welcome to your new app. + +<SurveyPrompt Title="How is Blazor working for you?" /> diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Program.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Program.cs new file mode 100644 index 0000000000000000000000000000000000000000..43b03d7ca81696246b48df5d03779b6cb1cef0b7 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Program.cs @@ -0,0 +1,75 @@ +using System; +using System.Net.Http; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Text; +#if (!NoAuth && Hosted) +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +#endif +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +#if (Hosted) +namespace ComponentsWebAssembly_CSharp.Client +#else +namespace ComponentsWebAssembly_CSharp +#endif +{ + public class Program + { + public static async Task Main(string[] args) + { + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add<App>("app"); + +#if (!Hosted || NoAuth) + builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); +#else + builder.Services.AddHttpClient("ComponentsWebAssembly_CSharp.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) + .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>(); + + // Supply HttpClient instances that include access tokens when making requests to the server project + builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ComponentsWebAssembly_CSharp.ServerAPI")); +#endif +#if(!NoAuth) + +#endif +#if (IndividualLocalAuth) + #if (Hosted) + builder.Services.AddApiAuthorization(); + #else + builder.Services.AddOidcAuthentication(options => + { + #if(MissingAuthority) + // Configure your authentication provider options here. + // For more information, see https://aka.ms/blazor-standalone-auth + #endif + builder.Configuration.Bind("Local", options.ProviderOptions); + }); + #endif +#endif +#if (IndividualB2CAuth) + builder.Services.AddMsalAuthentication(options => + { + builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication); +#if (Hosted) + options.ProviderOptions.DefaultAccessTokenScopes.Add("https://qualified.domain.name/api.id.uri/api-scope"); +#endif + }); +#endif +#if(OrganizationalAuth) + builder.Services.AddMsalAuthentication(options => + { + builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication); +#if (Hosted) + options.ProviderOptions.DefaultAccessTokenScopes.Add("api://api.id.uri/api-scope"); +#endif + }); +#endif + + await builder.Build().RunAsync(); + } + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Properties/launchSettings.json b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Properties/launchSettings.json new file mode 100644 index 0000000000000000000000000000000000000000..54eb43e3063fe5e205126f796ed0551c50378b41 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Properties/launchSettings.json @@ -0,0 +1,42 @@ +{ + "iisSettings": { + //#if (WindowsAuth) + "windowsAuthentication": true, + "anonymousAuthentication": false, + //#else + "windowsAuthentication": false, + "anonymousAuthentication": true, + //#endif + "iisExpress": { + "applicationUrl": "http://localhost:8080", + //#if(RequiresHttps) + "sslPort": 44300 + //#else + "sslPort": 0 + //#endif + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "ComponentsWebAssembly-CSharp": { + "commandName": "Project", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + //#if(RequiresHttps) + "applicationUrl": "https://localhost:5001;http://localhost:5000", + //#else + "applicationUrl": "http://localhost:5000", + //#endif + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/LoginDisplay.IndividualLocalAuth.razor b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/LoginDisplay.IndividualLocalAuth.razor new file mode 100644 index 0000000000000000000000000000000000000000..7cee41dcd07fb227e3a4961349bd2d041df78590 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/LoginDisplay.IndividualLocalAuth.razor @@ -0,0 +1,34 @@ +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication + +@inject NavigationManager Navigation +@inject SignOutSessionStateManager SignOutManager + +<AuthorizeView> +@*#if (Hosted) + <Authorized> + <a href="authentication/profile">Hello, @context.User.Identity.Name!</a> + <button class="nav-link btn btn-link" @onclick="BeginSignOut">Log out</button> + </Authorized> + <NotAuthorized> + <a href="authentication/register">Register</a> + <a href="authentication/login">Log in</a> + </NotAuthorized> + #else + <Authorized> + Hello, @context.User.Identity.Name! + <button class="nav-link btn btn-link" @onclick="BeginSignOut">Log out</button> + </Authorized> + <NotAuthorized> + <a href="authentication/login">Log in</a> + </NotAuthorized> +##endif*@ +</AuthorizeView> + +@code{ + private async Task BeginSignOut(MouseEventArgs args) + { + await SignOutManager.SetSignOutState(); + Navigation.NavigateTo("authentication/logout"); + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/LoginDisplay.IndividualMsalAuth.razor b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/LoginDisplay.IndividualMsalAuth.razor new file mode 100644 index 0000000000000000000000000000000000000000..1ecb6b2024723990da39232f77a8d7915524020b --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/LoginDisplay.IndividualMsalAuth.razor @@ -0,0 +1,23 @@ +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication + +@inject NavigationManager Navigation +@inject SignOutSessionStateManager SignOutManager + +<AuthorizeView> + <Authorized> + Hello, @context.User.Identity.Name! + <button class="nav-link btn btn-link" @onclick="BeginLogout">Log out</button> + </Authorized> + <NotAuthorized> + <a href="authentication/login">Log in</a> + </NotAuthorized> +</AuthorizeView> + +@code{ + private async Task BeginLogout(MouseEventArgs args) + { + await SignOutManager.SetSignOutState(); + Navigation.NavigateTo("authentication/logout"); + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/MainLayout.Auth.razor b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/MainLayout.Auth.razor new file mode 100644 index 0000000000000000000000000000000000000000..fafa2f55f1050ee5474776fd307632af46d594ce --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/MainLayout.Auth.razor @@ -0,0 +1,16 @@ +@inherits LayoutComponentBase + +<div class="sidebar"> + <NavMenu /> +</div> + +<div class="main"> + <div class="top-row px-4 auth"> + <LoginDisplay /> + <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a> + </div> + + <div class="content px-4"> + @Body + </div> +</div> diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/MainLayout.razor b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/MainLayout.NoAuth.razor similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/MainLayout.razor rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/MainLayout.NoAuth.razor diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/NavMenu.razor b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/NavMenu.razor similarity index 94% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/NavMenu.razor rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/NavMenu.razor index fbc5e1854f1ff1f615fbb0d2d1886d9caf1bec77..3ba4328a08f169113b32ed27dc00ffc6077813cb 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/NavMenu.razor +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/NavMenu.razor @@ -1,5 +1,5 @@ <div class="top-row pl-4 navbar navbar-dark"> - <a class="navbar-brand" href="">BlazorWasm-CSharp</a> + <a class="navbar-brand" href="">ComponentsWebAssembly-CSharp</a> <button class="navbar-toggler" @onclick="ToggleNavMenu"> <span class="navbar-toggler-icon"></span> </button> diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/RedirectToLogin.razor b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/RedirectToLogin.razor new file mode 100644 index 0000000000000000000000000000000000000000..b535ae4216f2b46752cc9f4d84fa921626094cc3 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/RedirectToLogin.razor @@ -0,0 +1,8 @@ +@inject NavigationManager Navigation +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@code { + protected override void OnInitialized() + { + Navigation.NavigateTo($"authentication/login?returnUrl={Uri.EscapeDataString(Navigation.Uri)}"); + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/SurveyPrompt.razor b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/SurveyPrompt.razor similarity index 88% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/SurveyPrompt.razor rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/SurveyPrompt.razor index adb69df8e84217f0a8a1b6fafa07eb9a707a4b63..02714098eff179ed775ca387d60f2353a7f2a8da 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/Shared/SurveyPrompt.razor +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/SurveyPrompt.razor @@ -4,7 +4,7 @@ <span class="text-nowrap"> Please take our - <a target="_blank" class="font-weight-bold" href="https://go.microsoft.com/fwlink/?linkid=2116045">brief survey</a> + <a target="_blank" class="font-weight-bold" href="https://go.microsoft.com/fwlink/?linkid=2127996">brief survey</a> </span> and tell us what you think. </div> diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/_Imports.razor b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/_Imports.razor new file mode 100644 index 0000000000000000000000000000000000000000..ac695bb5b1b21419ed5283482a9247ba6e20aa54 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/_Imports.razor @@ -0,0 +1,17 @@ +@using System.Net.Http +@using System.Net.Http.Json +@*#if (!NoAuth) +@using Microsoft.AspNetCore.Components.Authorization +#endif*@ +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@*#if (!Hosted) +@using ComponentsWebAssembly_CSharp +@using ComponentsWebAssembly_CSharp.Shared +#else +@using ComponentsWebAssembly_CSharp.Client +@using ComponentsWebAssembly_CSharp.Client.Shared +#endif*@ diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/appsettings.Development.json b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/appsettings.Development.json new file mode 100644 index 0000000000000000000000000000000000000000..e5be1aea222f7a7b891651397cc34f065097656c --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + ////#if (IndividualLocalAuth) + //"Local": { + // "Authority": "https://login.microsoftonline.com/", + // "ClientId": "33333333-3333-3333-33333333333333333" + //} + ////#endif +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/appsettings.json b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/appsettings.json new file mode 100644 index 0000000000000000000000000000000000000000..ad4a825523d8955ad6964a0298d5b2d253eeba68 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/appsettings.json @@ -0,0 +1,22 @@ +{ + ////#if (IndividualLocalAuth) + //"Local": { + // "Authority": "https://login.microsoftonline.com/", + // "ClientId": "33333333-3333-3333-33333333333333333" + //} + ////#endif + ////#if (IndividualB2CAuth) + //"AzureAdB2C": { + // "Authority": "https:////aadB2CInstance.b2clogin.com/qualified.domain.name/MySignUpSignInPolicyId", + // "ClientId": "33333333-3333-3333-33333333333333333", + // "ValidateAuthority": false + //} + ////#endif + ////#if (OrganizationalAuth) + //"AzureAd": { + // "Authority": "https:////login.microsoftonline.com/22222222-2222-2222-2222-222222222222", + // "ClientId": "33333333-3333-3333-33333333333333333", + // "ValidateAuthority": true + //} + ////#endif +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/site.css b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/app.css similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/site.css rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/app.css diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/bootstrap/bootstrap.min.css b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/bootstrap/bootstrap.min.css new file mode 100644 index 0000000000000000000000000000000000000000..92e3fe871295c44f8fa58ddc7ac242463f13e6bd --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/bootstrap/bootstrap.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.3.1 (https://getbootstrap.com/) + * Copyright 2011-2019 The Bootstrap Authors + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}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{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[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}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.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:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.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:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;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::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.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;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;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{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;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='%2328a745' 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:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.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)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' 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") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip{display:block}.form-control-file.is-valid~.valid-feedback,.form-control-file.is-valid~.valid-tooltip,.was-validated .form-control-file:valid~.valid-feedback,.was-validated .form-control-file:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;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;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.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' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);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 .2rem rgba(220,53,69,.25)}.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.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)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip{display:block}.form-control-file.is-invalid~.invalid-feedback,.form-control-file.is-invalid~.invalid-tooltip,.was-validated .form-control-file:invalid~.invalid-feedback,.was-validated .form-control-file:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;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;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.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;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 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-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{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}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .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}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .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}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;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:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .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{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;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:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{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")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:calc(1rem + .4rem);padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;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){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;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){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{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}.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{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;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;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;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}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;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>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;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>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;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>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;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>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;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>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.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,.5)}.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 .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.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,.5)}.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 .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;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:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;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-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;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:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-ms-flexbox;display:flex;-ms-flex:1 0 0%;flex:1 0 0%;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;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}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion>.card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion>.card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.accordion>.card .card-header{margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;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}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.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;line-height:1.5}.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:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;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){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;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:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.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:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.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:#007bff;border-color:#007bff}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-sm .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-md .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-lg .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-xl .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush .list-group-item:last-child{margin-bottom:-1px}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{margin-bottom:0;border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;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:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #dee2e6;border-bottom-right-radius:.3rem;border-bottom-left-radius:.3rem}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 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}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";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 .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;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:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";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 .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-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 #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;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:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-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-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:0s .6s opacity}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;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:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.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}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{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-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!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:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!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}.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}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!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}.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}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;overflow-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/bootstrap/bootstrap.min.css.map b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/bootstrap/bootstrap.min.css.map similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/bootstrap/bootstrap.min.css.map rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/bootstrap/bootstrap.min.css.map diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/FONT-LICENSE b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/FONT-LICENSE similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/FONT-LICENSE rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/FONT-LICENSE diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/ICON-LICENSE b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/ICON-LICENSE similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/ICON-LICENSE rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/ICON-LICENSE diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/README.md b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/README.md similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/README.md rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/README.md diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.eot b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.eot similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.eot rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.eot diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.otf b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.otf similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.otf rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.otf diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.svg b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.svg similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.svg rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.svg diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.woff b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.woff similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.woff rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.woff diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/favicon.ico b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a3a799985c43bc7309d701b2cad129023377dc71 Binary files /dev/null and b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/favicon.ico differ diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/icon-512.png b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..aae0bf71b26d7c8fd80358c2dca18c9b0bfec4a4 Binary files /dev/null and b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/icon-512.png differ diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/index.html b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/index.html new file mode 100644 index 0000000000000000000000000000000000000000..7e39788a581e8708018f964166b1230727b981d1 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/index.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html> + +<head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> + <title>ComponentsWebAssembly-CSharp</title> + <base href="/" /> + <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" /> + <link href="css/app.css" rel="stylesheet" /> + <!--#if PWA --> + <link href="manifest.json" rel="manifest" /> + <link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" /> + <!--#endif --> +</head> + +<body> + <app>Loading...</app> + + <div id="blazor-error-ui"> + An unhandled error has occurred. + <a href="" class="reload">Reload</a> + <a class="dismiss">🗙</a> + </div> +<!--#if (IndividualLocalAuth) --> + <script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script> +<!--#endif --> +<!--#if (IndividualB2CAuth || OrganizationalAuth) --> + <script src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationService.js"></script> +<!--#endif --> + <script src="_framework/blazor.webassembly.js"></script> + <!--#if PWA --> + <script>navigator.serviceWorker.register('service-worker.js');</script> + <!--#endif --> +</body> + +</html> diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/manifest.json b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..47ef2962278e4bf51d8ba7535c348b28ec63466d --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/manifest.json @@ -0,0 +1,15 @@ +{ + "name": "ComponentsWebAssembly-CSharp", + "short_name": "ComponentsWebAssembly-CSharp", + "start_url": "./", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#03173d", + "icons": [ + { + "src": "icon-512.png", + "type": "image/png", + "sizes": "512x512" + } + ] +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/sample-data/weather.json b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/sample-data/weather.json similarity index 55% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/sample-data/weather.json rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/sample-data/weather.json index ab28bc13ac18758719f690150322557ab8c13ae8..06463c02f11fdb96b753f0b216df2af65f5c7c2c 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Client/wwwroot/sample-data/weather.json +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/sample-data/weather.json @@ -2,31 +2,26 @@ { "date": "2018-05-06", "temperatureC": 1, - "summary": "Freezing", - "temperatureF": 33 + "summary": "Freezing" }, { "date": "2018-05-07", "temperatureC": 14, - "summary": "Bracing", - "temperatureF": 57 + "summary": "Bracing" }, { "date": "2018-05-08", "temperatureC": -13, - "summary": "Freezing", - "temperatureF": 9 + "summary": "Freezing" }, { "date": "2018-05-09", "temperatureC": -16, - "summary": "Balmy", - "temperatureF": 4 + "summary": "Balmy" }, { "date": "2018-05-10", "temperatureC": -2, - "summary": "Chilly", - "temperatureF": 29 + "summary": "Chilly" } ] diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/service-worker.js b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/service-worker.js new file mode 100644 index 0000000000000000000000000000000000000000..fe614daee090c3308424ce6733afdeb094840740 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/service-worker.js @@ -0,0 +1,4 @@ +// In development, always fetch from the network and do not enable offline support. +// This is because caching would make development more difficult (changes would not +// be reflected on the first load after each change). +self.addEventListener('fetch', () => { }); diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/service-worker.published.js b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/service-worker.published.js new file mode 100644 index 0000000000000000000000000000000000000000..755b7b944997b9013114e5cc0d85fb685c450c75 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/wwwroot/service-worker.published.js @@ -0,0 +1,60 @@ +// Caution! Be sure you understand the caveats before publishing an application with +// offline support. See https://aka.ms/blazor-offline-considerations + +self.importScripts('./service-worker-assets.js'); +self.addEventListener('install', event => event.waitUntil(onInstall(event))); +self.addEventListener('activate', event => event.waitUntil(onActivate(event))); +self.addEventListener('fetch', event => event.respondWith(onFetch(event))); + +const cacheNamePrefix = 'offline-cache-'; +const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`; +const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/ ]; +const offlineAssetsExclude = [ /^service-worker\.js$/ ]; + +async function onInstall(event) { + console.info('Service worker: Install'); + + // Fetch and cache all matching items from the assets manifest + const assetsRequests = self.assetsManifest.assets + .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url))) + .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url))) + .map(asset => new Request(asset.url, { integrity: asset.hash })); +//#if(IndividualLocalAuth && Hosted) + + // Also cache authentication configuration + assetsRequests.push(new Request('_configuration/ComponentsWebAssembly-CSharp.Client')); + +//#endif + await caches.open(cacheName).then(cache => cache.addAll(assetsRequests)); +} + +async function onActivate(event) { + console.info('Service worker: Activate'); + + // Delete unused caches + const cacheKeys = await caches.keys(); + await Promise.all(cacheKeys + .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName) + .map(key => caches.delete(key))); +} + +async function onFetch(event) { + let cachedResponse = null; + if (event.request.method === 'GET') { + // For all navigation requests, try to serve index.html from cache + // If you need some URLs to be server-rendered, edit the following check to exclude those URLs +//#if(IndividualLocalAuth && Hosted) + const shouldServeIndexHtml = event.request.mode === 'navigate' + && !event.request.url.includes('/connect/') + && !event.request.url.includes('/Identity/'); +//#else + const shouldServeIndexHtml = event.request.mode === 'navigate'; +//#endif + + const request = shouldServeIndexHtml ? 'index.html' : event.request; + const cache = await caches.open(cacheName); + cachedResponse = await cache.match(request); + } + + return cachedResponse || fetch(event.request); +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/BlazorWasm-CSharp.sln b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/ComponentsWebAssembly-CSharp.sln similarity index 86% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/BlazorWasm-CSharp.sln rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/ComponentsWebAssembly-CSharp.sln index 93e94580898f8cb007660f7d72df4b47389b9e23..e8cedfa73a97eec03a437da7230a479d4ec19489 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/BlazorWasm-CSharp.sln +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/ComponentsWebAssembly-CSharp.sln @@ -2,11 +2,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.0.0 MinimumVisualStudioVersion = 16.0.0.0 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorWasm-CSharp.Server", "Server\BlazorWasm-CSharp.Server.csproj", "{650B3CE7-2E93-4CC4-9F46-466686815EAA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsWebAssembly-CSharp.Server", "Server\ComponentsWebAssembly-CSharp.Server.csproj", "{650B3CE7-2E93-4CC4-9F46-466686815EAA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorWasm-CSharp.Client", "Client\BlazorWasm-CSharp.Client.csproj", "{5990939C-7E7B-4CFA-86FF-44CA5756498A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsWebAssembly-CSharp.Client", "Client\ComponentsWebAssembly-CSharp.Client.csproj", "{5990939C-7E7B-4CFA-86FF-44CA5756498A}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorWasm-CSharp.Shared", "Shared\BlazorWasm-CSharp.Shared.csproj", "{0AFFA7FD-4E37-4636-AB91-3753E746DB98}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ComponentsWebAssembly-CSharp.Shared", "Shared\ComponentsWebAssembly-CSharp.Shared.csproj", "{0AFFA7FD-4E37-4636-AB91-3753E746DB98}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -61,4 +61,4 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4C26868E-5E7C-458D-82E3-040509D0C71F} EndGlobalSection -EndGlobal +EndGlobal \ No newline at end of file diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml new file mode 100644 index 0000000000000000000000000000000000000000..913f4c479bf7039c20e5caaaab51f0aa9dbc9954 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml @@ -0,0 +1,35 @@ +@using Microsoft.AspNetCore.Identity +@using ComponentsWebAssembly_CSharp.Server.Models +@inject SignInManager<ApplicationUser> SignInManager +@inject UserManager<ApplicationUser> UserManager +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + +@{ + var returnUrl = "/"; + if (Context.Request.Query.TryGetValue("returnUrl", out var existingUrl)) { + returnUrl = existingUrl; + } +} + +<ul class="navbar-nav"> +@if (SignInManager.IsSignedIn(User)) +{ + <li class="nav-item"> + <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a> + </li> + <li class="nav-item"> + <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="/" method="post"> + <button type="submit" class="nav-link btn btn-link text-dark">Logout</button> + </form> + </li> +} +else +{ + <li class="nav-item"> + <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register" asp-route-returnUrl="@returnUrl">Register</a> + </li> + <li class="nav-item"> + <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login" asp-route-returnUrl="@returnUrl">Login</a> + </li> +} +</ul> diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Controllers/OidcConfigurationController.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Controllers/OidcConfigurationController.cs new file mode 100644 index 0000000000000000000000000000000000000000..273fda395ccc38015c5cae226a3f9d719b68e006 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Controllers/OidcConfigurationController.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace ComponentsWebAssembly_CSharp.Server.Controllers +{ + public class OidcConfigurationController : Controller + { + private readonly ILogger<OidcConfigurationController> _logger; + + public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger<OidcConfigurationController> logger) + { + ClientRequestParametersProvider = clientRequestParametersProvider; + _logger = logger; + } + + public IClientRequestParametersProvider ClientRequestParametersProvider { get; } + + [HttpGet("_configuration/{clientId}")] + public IActionResult GetClientRequestParameters([FromRoute]string clientId) + { + var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId); + return Ok(parameters); + } + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Controllers/WeatherForecastController.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000000000000000000000000000000000000..117055576dec220ae4373678fffeb547a7acbbb8 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Controllers/WeatherForecastController.cs @@ -0,0 +1,46 @@ +using ComponentsWebAssembly_CSharp.Shared; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +#if (!NoAuth) +using Microsoft.AspNetCore.Authorization; +#endif +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace ComponentsWebAssembly_CSharp.Server.Controllers +{ +#if (!NoAuth) + [Authorize] +#endif + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger<WeatherForecastController> logger; + + public WeatherForecastController(ILogger<WeatherForecastController> logger) + { + this.logger = logger; + } + + [HttpGet] + public IEnumerable<WeatherForecast> Get() + { + var rng = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = rng.Next(-20, 55), + Summary = Summaries[rng.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/ApplicationDbContext.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/ApplicationDbContext.cs new file mode 100644 index 0000000000000000000000000000000000000000..ed95c4aba5a8a9e13aa5d003a2d2a6389050d94a --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/ApplicationDbContext.cs @@ -0,0 +1,21 @@ +using ComponentsWebAssembly_CSharp.Server.Models; +using IdentityServer4.EntityFramework.Options; +using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ComponentsWebAssembly_CSharp.Server.Data +{ + public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser> + { + public ApplicationDbContext( + DbContextOptions options, + IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions) + { + } + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..414379d1c2a7607ac2b701b97eb8d9a5dbc2eb7e --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs @@ -0,0 +1,352 @@ +// <auto-generated /> +using System; +using ComponentsWebAssembly_CSharp.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ComponentsWebAssembly_CSharp.Server.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("00000000000000_CreateIdentitySchema")] + partial class CreateIdentitySchema + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.0-rc1.19455.8"); + + modelBuilder.Entity("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", b => + { + b.Property<string>("Id") + .HasColumnType("TEXT"); + + b.Property<int>("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property<string>("Email") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<bool>("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property<bool>("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property<DateTimeOffset?>("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property<string>("NormalizedEmail") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<string>("NormalizedUserName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<string>("PasswordHash") + .HasColumnType("TEXT"); + + b.Property<string>("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property<bool>("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property<string>("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property<bool>("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property<string>("UserName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property<string>("UserCode") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<string>("ClientId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<DateTime>("CreationTime") + .HasColumnType("TEXT"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(50000); + + b.Property<string>("DeviceCode") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<DateTime?>("Expiration") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<string>("SubjectId") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property<string>("Key") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<string>("ClientId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<DateTime>("CreationTime") + .HasColumnType("TEXT"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(50000); + + b.Property<DateTime?>("Expiration") + .HasColumnType("TEXT"); + + b.Property<string>("SubjectId") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<string>("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(50); + + b.HasKey("Key"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.ToTable("PersistedGrants"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property<string>("Id") + .HasColumnType("TEXT"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<string>("NormalizedName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("ClaimType") + .HasColumnType("TEXT"); + + b.Property<string>("ClaimValue") + .HasColumnType("TEXT"); + + b.Property<string>("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("ClaimType") + .HasColumnType("TEXT"); + + b.Property<string>("ClaimValue") + .HasColumnType("TEXT"); + + b.Property<string>("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => + { + b.Property<string>("LoginProvider") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("ProviderKey") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property<string>("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => + { + b.Property<string>("UserId") + .HasColumnType("TEXT"); + + b.Property<string>("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => + { + b.Property<string>("UserId") + .HasColumnType("TEXT"); + + b.Property<string>("LoginProvider") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("Name") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => + { + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => + { + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => + { + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlLite/00000000000000_CreateIdentitySchema.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlLite/00000000000000_CreateIdentitySchema.cs new file mode 100644 index 0000000000000000000000000000000000000000..a1b61ddc1e1dfe10a0b8da45609a6b24bd9ceb0a --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlLite/00000000000000_CreateIdentitySchema.cs @@ -0,0 +1,278 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ComponentsWebAssembly_CSharp.Server.Data.Migrations +{ + public partial class CreateIdentitySchema : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column<string>(nullable: false), + Name = table.Column<string>(maxLength: 256, nullable: true), + NormalizedName = table.Column<string>(maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column<string>(nullable: false), + UserName = table.Column<string>(maxLength: 256, nullable: true), + NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true), + Email = table.Column<string>(maxLength: 256, nullable: true), + NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true), + EmailConfirmed = table.Column<bool>(nullable: false), + PasswordHash = table.Column<string>(nullable: true), + SecurityStamp = table.Column<string>(nullable: true), + ConcurrencyStamp = table.Column<string>(nullable: true), + PhoneNumber = table.Column<string>(nullable: true), + PhoneNumberConfirmed = table.Column<bool>(nullable: false), + TwoFactorEnabled = table.Column<bool>(nullable: false), + LockoutEnd = table.Column<DateTimeOffset>(nullable: true), + LockoutEnabled = table.Column<bool>(nullable: false), + AccessFailedCount = table.Column<int>(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "DeviceCodes", + columns: table => new + { + UserCode = table.Column<string>(maxLength: 200, nullable: false), + DeviceCode = table.Column<string>(maxLength: 200, nullable: false), + SubjectId = table.Column<string>(maxLength: 200, nullable: true), + ClientId = table.Column<string>(maxLength: 200, nullable: false), + CreationTime = table.Column<DateTime>(nullable: false), + Expiration = table.Column<DateTime>(nullable: false), + Data = table.Column<string>(maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DeviceCodes", x => x.UserCode); + }); + + migrationBuilder.CreateTable( + name: "PersistedGrants", + columns: table => new + { + Key = table.Column<string>(maxLength: 200, nullable: false), + Type = table.Column<string>(maxLength: 50, nullable: false), + SubjectId = table.Column<string>(maxLength: 200, nullable: true), + ClientId = table.Column<string>(maxLength: 200, nullable: false), + CreationTime = table.Column<DateTime>(nullable: false), + Expiration = table.Column<DateTime>(nullable: true), + Data = table.Column<string>(maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PersistedGrants", x => x.Key); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column<int>(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoleId = table.Column<string>(nullable: false), + ClaimType = table.Column<string>(nullable: true), + ClaimValue = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column<int>(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column<string>(nullable: false), + ClaimType = table.Column<string>(nullable: true), + ClaimValue = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column<string>(maxLength: 128, nullable: false), + ProviderKey = table.Column<string>(maxLength: 128, nullable: false), + ProviderDisplayName = table.Column<string>(nullable: true), + UserId = table.Column<string>(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column<string>(nullable: false), + RoleId = table.Column<string>(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column<string>(nullable: false), + LoginProvider = table.Column<string>(maxLength: 128, nullable: false), + Name = table.Column<string>(maxLength: 128, nullable: false), + Value = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DeviceCodes_DeviceCode", + table: "DeviceCodes", + column: "DeviceCode", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DeviceCodes_Expiration", + table: "DeviceCodes", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_Expiration", + table: "PersistedGrants", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_SubjectId_ClientId_Type", + table: "PersistedGrants", + columns: new[] { "SubjectId", "ClientId", "Type" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "DeviceCodes"); + + migrationBuilder.DropTable( + name: "PersistedGrants"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlLite/ApplicationDbContextModelSnapshot.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlLite/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000000000000000000000000000000000000..8352792068c185248da2f0a5ed7c9c4f8dc1ef1b --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlLite/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,350 @@ +// <auto-generated /> +using System; +using ComponentsWebAssembly_CSharp.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ComponentsWebAssembly_CSharp.Server.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.0-rc1.19455.8"); + + modelBuilder.Entity("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", b => + { + b.Property<string>("Id") + .HasColumnType("TEXT"); + + b.Property<int>("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property<string>("Email") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<bool>("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property<bool>("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property<DateTimeOffset?>("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property<string>("NormalizedEmail") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<string>("NormalizedUserName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<string>("PasswordHash") + .HasColumnType("TEXT"); + + b.Property<string>("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property<bool>("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property<string>("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property<bool>("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property<string>("UserName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property<string>("UserCode") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<string>("ClientId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<DateTime>("CreationTime") + .HasColumnType("TEXT"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(50000); + + b.Property<string>("DeviceCode") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<DateTime?>("Expiration") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property<string>("SubjectId") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property<string>("Key") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<string>("ClientId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<DateTime>("CreationTime") + .HasColumnType("TEXT"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(50000); + + b.Property<DateTime?>("Expiration") + .HasColumnType("TEXT"); + + b.Property<string>("SubjectId") + .HasColumnType("TEXT") + .HasMaxLength(200); + + b.Property<string>("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(50); + + b.HasKey("Key"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.ToTable("PersistedGrants"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property<string>("Id") + .HasColumnType("TEXT"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property<string>("Name") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property<string>("NormalizedName") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("ClaimType") + .HasColumnType("TEXT"); + + b.Property<string>("ClaimValue") + .HasColumnType("TEXT"); + + b.Property<string>("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property<string>("ClaimType") + .HasColumnType("TEXT"); + + b.Property<string>("ClaimValue") + .HasColumnType("TEXT"); + + b.Property<string>("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => + { + b.Property<string>("LoginProvider") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("ProviderKey") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property<string>("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => + { + b.Property<string>("UserId") + .HasColumnType("TEXT"); + + b.Property<string>("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => + { + b.Property<string>("UserId") + .HasColumnType("TEXT"); + + b.Property<string>("LoginProvider") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("Name") + .HasColumnType("TEXT") + .HasMaxLength(128); + + b.Property<string>("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => + { + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => + { + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => + { + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs new file mode 100644 index 0000000000000000000000000000000000000000..9b11b6b060997b82d1c0bb6e39b483791e7f7673 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs @@ -0,0 +1,359 @@ +// <auto-generated /> +using System; +using ComponentsWebAssembly_CSharp.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ComponentsWebAssembly_CSharp.Server.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("00000000000000_CreateIdentitySchema")] + partial class CreateIdentitySchema + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.0-rc1.19455.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", b => + { + b.Property<string>("Id") + .HasColumnType("nvarchar(450)"); + + b.Property<int>("AccessFailedCount") + .HasColumnType("int"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property<string>("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property<bool>("EmailConfirmed") + .HasColumnType("bit"); + + b.Property<bool>("LockoutEnabled") + .HasColumnType("bit"); + + b.Property<DateTimeOffset?>("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property<string>("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property<string>("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property<string>("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property<string>("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property<bool>("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property<string>("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property<bool>("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property<string>("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property<string>("UserCode") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property<string>("ClientId") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property<DateTime>("CreationTime") + .HasColumnType("datetime2"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(50000); + + b.Property<string>("DeviceCode") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property<DateTime?>("Expiration") + .IsRequired() + .HasColumnType("datetime2"); + + b.Property<string>("SubjectId") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property<string>("Key") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property<string>("ClientId") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property<DateTime>("CreationTime") + .HasColumnType("datetime2"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(50000); + + b.Property<DateTime?>("Expiration") + .HasColumnType("datetime2"); + + b.Property<string>("SubjectId") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property<string>("Type") + .IsRequired() + .HasColumnType("nvarchar(50)") + .HasMaxLength(50); + + b.HasKey("Key"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.ToTable("PersistedGrants"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property<string>("Id") + .HasColumnType("nvarchar(450)"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property<string>("Name") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property<string>("NormalizedName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property<string>("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property<string>("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property<string>("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property<string>("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property<string>("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property<string>("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => + { + b.Property<string>("LoginProvider") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property<string>("ProviderKey") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property<string>("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property<string>("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => + { + b.Property<string>("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property<string>("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => + { + b.Property<string>("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property<string>("LoginProvider") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property<string>("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property<string>("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => + { + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => + { + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => + { + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlServer/00000000000000_CreateIdentitySchema.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlServer/00000000000000_CreateIdentitySchema.cs new file mode 100644 index 0000000000000000000000000000000000000000..00ba7147171ace2c5a728e3dfe57a094df639a21 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlServer/00000000000000_CreateIdentitySchema.cs @@ -0,0 +1,280 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace ComponentsWebAssembly_CSharp.Server.Data.Migrations +{ + public partial class CreateIdentitySchema : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column<string>(nullable: false), + Name = table.Column<string>(maxLength: 256, nullable: true), + NormalizedName = table.Column<string>(maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column<string>(nullable: false), + UserName = table.Column<string>(maxLength: 256, nullable: true), + NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true), + Email = table.Column<string>(maxLength: 256, nullable: true), + NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true), + EmailConfirmed = table.Column<bool>(nullable: false), + PasswordHash = table.Column<string>(nullable: true), + SecurityStamp = table.Column<string>(nullable: true), + ConcurrencyStamp = table.Column<string>(nullable: true), + PhoneNumber = table.Column<string>(nullable: true), + PhoneNumberConfirmed = table.Column<bool>(nullable: false), + TwoFactorEnabled = table.Column<bool>(nullable: false), + LockoutEnd = table.Column<DateTimeOffset>(nullable: true), + LockoutEnabled = table.Column<bool>(nullable: false), + AccessFailedCount = table.Column<int>(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "DeviceCodes", + columns: table => new + { + UserCode = table.Column<string>(maxLength: 200, nullable: false), + DeviceCode = table.Column<string>(maxLength: 200, nullable: false), + SubjectId = table.Column<string>(maxLength: 200, nullable: true), + ClientId = table.Column<string>(maxLength: 200, nullable: false), + CreationTime = table.Column<DateTime>(nullable: false), + Expiration = table.Column<DateTime>(nullable: false), + Data = table.Column<string>(maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DeviceCodes", x => x.UserCode); + }); + + migrationBuilder.CreateTable( + name: "PersistedGrants", + columns: table => new + { + Key = table.Column<string>(maxLength: 200, nullable: false), + Type = table.Column<string>(maxLength: 50, nullable: false), + SubjectId = table.Column<string>(maxLength: 200, nullable: true), + ClientId = table.Column<string>(maxLength: 200, nullable: false), + CreationTime = table.Column<DateTime>(nullable: false), + Expiration = table.Column<DateTime>(nullable: true), + Data = table.Column<string>(maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PersistedGrants", x => x.Key); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column<int>(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column<string>(nullable: false), + ClaimType = table.Column<string>(nullable: true), + ClaimValue = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column<int>(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column<string>(nullable: false), + ClaimType = table.Column<string>(nullable: true), + ClaimValue = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column<string>(maxLength: 128, nullable: false), + ProviderKey = table.Column<string>(maxLength: 128, nullable: false), + ProviderDisplayName = table.Column<string>(nullable: true), + UserId = table.Column<string>(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column<string>(nullable: false), + RoleId = table.Column<string>(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column<string>(nullable: false), + LoginProvider = table.Column<string>(maxLength: 128, nullable: false), + Name = table.Column<string>(maxLength: 128, nullable: false), + Value = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); + + migrationBuilder.CreateIndex( + name: "IX_DeviceCodes_DeviceCode", + table: "DeviceCodes", + column: "DeviceCode", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DeviceCodes_Expiration", + table: "DeviceCodes", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_Expiration", + table: "PersistedGrants", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_SubjectId_ClientId_Type", + table: "PersistedGrants", + columns: new[] { "SubjectId", "ClientId", "Type" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "DeviceCodes"); + + migrationBuilder.DropTable( + name: "PersistedGrants"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlServer/ApplicationDbContextModelSnapshot.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlServer/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000000000000000000000000000000000000..8df4c8457da64d5859732f153207be0441b734d7 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Data/SqlServer/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,357 @@ +// <auto-generated /> +using System; +using ComponentsWebAssembly_CSharp.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace ComponentsWebAssembly_CSharp.Server.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.0-rc1.19455.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", b => + { + b.Property<string>("Id") + .HasColumnType("nvarchar(450)"); + + b.Property<int>("AccessFailedCount") + .HasColumnType("int"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property<string>("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property<bool>("EmailConfirmed") + .HasColumnType("bit"); + + b.Property<bool>("LockoutEnabled") + .HasColumnType("bit"); + + b.Property<DateTimeOffset?>("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property<string>("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property<string>("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property<string>("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property<string>("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property<bool>("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property<string>("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property<bool>("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property<string>("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property<string>("UserCode") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property<string>("ClientId") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property<DateTime>("CreationTime") + .HasColumnType("datetime2"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(50000); + + b.Property<string>("DeviceCode") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property<DateTime?>("Expiration") + .IsRequired() + .HasColumnType("datetime2"); + + b.Property<string>("SubjectId") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property<string>("Key") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property<string>("ClientId") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property<DateTime>("CreationTime") + .HasColumnType("datetime2"); + + b.Property<string>("Data") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(50000); + + b.Property<DateTime?>("Expiration") + .HasColumnType("datetime2"); + + b.Property<string>("SubjectId") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property<string>("Type") + .IsRequired() + .HasColumnType("nvarchar(50)") + .HasMaxLength(50); + + b.HasKey("Key"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.ToTable("PersistedGrants"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property<string>("Id") + .HasColumnType("nvarchar(450)"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property<string>("Name") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property<string>("NormalizedName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property<string>("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property<string>("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property<string>("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property<string>("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property<string>("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property<string>("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => + { + b.Property<string>("LoginProvider") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property<string>("ProviderKey") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property<string>("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property<string>("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => + { + b.Property<string>("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property<string>("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => + { + b.Property<string>("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property<string>("LoginProvider") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property<string>("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property<string>("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => + { + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => + { + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => + { + b.HasOne("ComponentsWebAssembly_CSharp.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Models/ApplicationUser.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Models/ApplicationUser.cs new file mode 100644 index 0000000000000000000000000000000000000000..216c5b31692d77e7ba44595cbf82b264b3eed66f --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Models/ApplicationUser.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Identity; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ComponentsWebAssembly_CSharp.Server.Models +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Pages/Error.cshtml b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Pages/Error.cshtml new file mode 100644 index 0000000000000000000000000000000000000000..acc46468b65251d86ffd67c000b3e77a846b75fd --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Pages/Error.cshtml @@ -0,0 +1,27 @@ +@page +@model ComponentsWebAssembly_CSharp.Server.Pages.ErrorModel +@{ + Layout = "_Layout"; + ViewData["Title"] = "Error"; +} + +<h1 class="text-danger">Error.</h1> +<h2 class="text-danger">An error occurred while processing your request.</h2> + +@if (Model.ShowRequestId) +{ + <p> + <strong>Request ID:</strong> <code>@Model.RequestId</code> + </p> +} + +<h3>Development Mode</h3> +<p> + Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred. +</p> +<p> + <strong>The Development environment shouldn't be enabled for deployed applications.</strong> + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong> + and restarting the app. +</p> diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Pages/Error.cshtml.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Pages/Error.cshtml.cs new file mode 100644 index 0000000000000000000000000000000000000000..3867a3976de128be2557c58e926dc8d81f8c8b53 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Pages/Error.cshtml.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace ComponentsWebAssembly_CSharp.Server.Pages +{ + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public class ErrorModel : PageModel + { + public string RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + private readonly ILogger<ErrorModel> _logger; + + public ErrorModel(ILogger<ErrorModel> logger) + { + _logger = logger; + } + + public void OnGet() + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; + } + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Pages/Shared/_Layout.cshtml b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Pages/Shared/_Layout.cshtml new file mode 100644 index 0000000000000000000000000000000000000000..a3695776cc783c9df6aa9d0015f4eb125f6941c6 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Pages/Shared/_Layout.cshtml @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> + +<head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> + <title>@ViewBag.Title</title> + <link href="~/css/bootstrap/bootstrap.min.css" rel="stylesheet" /> + <link href="~/css/app.css" rel="stylesheet" /> +</head> + +<body> + <div class="main"> + <div class="content px-4"> + @RenderBody() + </div> + </div> +</body> + +</html> diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Program.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Program.cs new file mode 100644 index 0000000000000000000000000000000000000000..0f0c06165788fd6870640475cf8b4638e9112e67 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace ComponentsWebAssembly_CSharp.Server +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup<Startup>(); + }); + } +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Properties/launchSettings.json b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Properties/launchSettings.json similarity index 75% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Properties/launchSettings.json rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Properties/launchSettings.json index 3cf9f85e48c0718a7162028811bf3080fe3b44d6..578c5e9c1cef6676ee9065ac07a57e983b908fcd 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Server/Properties/launchSettings.json +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Properties/launchSettings.json @@ -1,5 +1,4 @@ { - "useWebAssemblyDebugging": true, "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, @@ -16,13 +15,15 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, - "Company.WebApplication1": { + "ComponentsWebAssembly-CSharp.Server": { "commandName": "Project", "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", //#if(RequiresHttps) "applicationUrl": "https://localhost:5001;http://localhost:5000", //#else diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Startup.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Startup.cs new file mode 100644 index 0000000000000000000000000000000000000000..cfac34ab3126b45d3d861342dfa9682d6cf37d10 --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/Startup.cs @@ -0,0 +1,126 @@ +#if (OrganizationalAuth || IndividualB2CAuth || IndividualLocalAuth) +using Microsoft.AspNetCore.Authentication; +#endif +#if (OrganizationalAuth) +using Microsoft.AspNetCore.Authentication.AzureAD.UI; +#endif +#if (IndividualB2CAuth) +using Microsoft.AspNetCore.Authentication.AzureADB2C.UI; +#endif +using Microsoft.AspNetCore.Builder; +#if (IndividualLocalAuth) +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI; +#endif +#if (RequiresHttps) +using Microsoft.AspNetCore.HttpsPolicy; +#endif +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.ResponseCompression; +#if (IndividualLocalAuth) +using Microsoft.EntityFrameworkCore; +#endif +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System.Linq; +#if (IndividualLocalAuth) +using ComponentsWebAssembly_CSharp.Server.Data; +using ComponentsWebAssembly_CSharp.Server.Models; +#endif + +namespace ComponentsWebAssembly_CSharp.Server +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { +#if (IndividualLocalAuth) + services.AddDbContext<ApplicationDbContext>(options => + #if (UseLocalDB) + options.UseSqlServer( + Configuration.GetConnectionString("DefaultConnection"))); + #else + options.UseSqlite( + Configuration.GetConnectionString("DefaultConnection"))); + #endif + + services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores<ApplicationDbContext>(); + + services.AddIdentityServer() + .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(); + + services.AddAuthentication() + .AddIdentityServerJwt(); +#endif +#if (OrganizationalAuth) + services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme) + .AddAzureADBearer(options => Configuration.Bind("AzureAd", options)); +#elif (IndividualB2CAuth) + services.AddAuthentication(AzureADB2CDefaults.BearerAuthenticationScheme) + .AddAzureADB2CBearer(options => Configuration.Bind("AzureAdB2C", options)); +#endif + + services.AddControllersWithViews(); + services.AddRazorPages(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); +#if (IndividualLocalAuth) + app.UseDatabaseErrorPage(); +#endif + app.UseWebAssemblyDebugging(); + } + else + { + app.UseExceptionHandler("/Error"); +#if (RequiresHttps) + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); +#else + } + +#endif + app.UseBlazorFrameworkFiles(); + app.UseStaticFiles(); + + app.UseRouting(); + +#if (IndividualLocalAuth) + app.UseIdentityServer(); +#endif +#if (OrganizationalAuth || IndividualAuth) + app.UseAuthentication(); +#endif +#if (!NoAuth) + app.UseAuthorization(); + +#endif + app.UseEndpoints(endpoints => + { + endpoints.MapRazorPages(); + endpoints.MapControllers(); + endpoints.MapFallbackToFile("index.html"); + }); + } + } +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/app.db b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/app.db new file mode 100644 index 0000000000000000000000000000000000000000..1f4261428921fa7da2622f12eeb1399f23023b2c Binary files /dev/null and b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/app.db differ diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/appsettings.Development.json b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/appsettings.Development.json new file mode 100644 index 0000000000000000000000000000000000000000..a1213fe246ba8d88a66c53f8ff4b245241bcfe9a --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/appsettings.Development.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } +////#if (IndividualLocalAuth) +// }, +// "IdentityServer": { +// "Key": { +// "Type": "Development" +// } +// } +////#else +// } +////#endif +} diff --git a/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/appsettings.json b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/appsettings.json new file mode 100644 index 0000000000000000000000000000000000000000..fe7926d97302f264d0675d8ec0bcd9f6d252b77c --- /dev/null +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Server/appsettings.json @@ -0,0 +1,42 @@ +{ +////#if (IndividualLocalAuth) +// "ConnectionStrings": { +////#if (UseLocalDB) +// "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-ComponentsWebAssembly_CSharp.Server-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true" +////#else +// "DefaultConnection": "DataSource=app.db" +////#endif +// }, +////#elseif (IndividualB2CAuth) +// "AzureAdB2C": { +// "Instance": "https:////aadB2CInstance.b2clogin.com/", +// "ClientId": "11111111-1111-1111-11111111111111111", +// "Domain": "qualified.domain.name", +// "SignUpSignInPolicyId": "MySignUpSignInPolicyId" +// }, +////#elseif (OrganizationalAuth) +// "AzureAd": { +// "Instance": "https:////login.microsoftonline.com/", +// "Domain": "qualified.domain.name", +// "TenantId": "22222222-2222-2222-2222-222222222222", +// "ClientId": "11111111-1111-1111-11111111111111111", +// }, +////#endif + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, +////#if (IndividualLocalAuth) +// "IdentityServer": { +// "Clients": { +// "ComponentsWebAssembly_CSharp.Client": { +// "Profile": "IdentityServerSPA" +// } +// } +// }, +////#endif +"AllowedHosts": "*" +} diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Shared/WeatherForecast.cs b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Shared/WeatherForecast.cs similarity index 87% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Shared/WeatherForecast.cs rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Shared/WeatherForecast.cs index f845d1c4b17d9559d5826a0c12e80f11b346d02b..32e3d002de0be9739cfe5ab425a91484659f66b5 100644 --- a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/BlazorWasm-CSharp/Shared/WeatherForecast.cs +++ b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Shared/WeatherForecast.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace BlazorWasm_CSharp.Shared +namespace ComponentsWebAssembly_CSharp.Shared { public class WeatherForecast { diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/Directory.Build.props b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/Directory.Build.props similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/Directory.Build.props rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/Directory.Build.props diff --git a/src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/Directory.Build.targets b/src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/Directory.Build.targets similarity index 100% rename from src/ProjectTemplates/BlazorWasm.ProjectTemplates/content/Directory.Build.targets rename to src/ProjectTemplates/ComponentsWebAssembly.ProjectTemplates/content/Directory.Build.targets diff --git a/src/ProjectTemplates/ProjectTemplates.sln b/src/ProjectTemplates/ProjectTemplates.sln index 7628d8233da898009e476cd75f25bd51bec2acfa..97f325781974dfa770a1a1b905f7a0ccfa11daeb 100644 --- a/src/ProjectTemplates/ProjectTemplates.sln +++ b/src/ProjectTemplates/ProjectTemplates.sln @@ -3,14 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28627.84 MinimumVisualStudioVersion = 15.0.26124.0 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Web.ProjectTemplates", "Web.ProjectTemplates\Microsoft.DotNet.Web.ProjectTemplates.csproj", "{3D3DE8B3-6B54-4CF4-82B0-718E0009A4E5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Web.Spa.ProjectTemplates", "Web.Spa.ProjectTemplates\Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj", "{96251C41-7953-46DC-B131-5A070640959A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Web.Client.ItemTemplates", "Web.Client.ItemTemplates\Microsoft.DotNet.Web.Client.ItemTemplates.csproj", "{92F0615B-4C8F-456C-86C0-39384BB7031E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Web.ItemTemplates", "Web.ItemTemplates\Microsoft.DotNet.Web.ItemTemplates.csproj", "{BC03F087-9B6F-4A66-9571-B0C5C204A101}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjectTemplates.Tests", "test\ProjectTemplates.Tests.csproj", "{AF371A60-8A85-4ADF-BE44-0F2B94234DB1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetToolsInstaller", "testassets\DotNetToolsInstaller\DotNetToolsInstaller.csproj", "{4B971DBF-6B07-4DC5-914D-4D5681F220CC}" @@ -177,7 +169,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.ApiAut EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.SpaServices.Extensions", "..\Middleware\SpaServices.Extensions\src\Microsoft.AspNetCore.SpaServices.Extensions.csproj", "{06D0D7B2-EDA3-45A2-A060-AB791ED1DB80}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Blazor.Templates", "BlazorWasm.ProjectTemplates\Microsoft.AspNetCore.Blazor.Templates.csproj", "{C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.WebAssembly.Templates", "ComponentsWebAssembly.ProjectTemplates\Microsoft.AspNetCore.Components.WebAssembly.Templates.csproj", "{F2870943-14EA-4AD2-AC01-E4E66E0B63F4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -189,54 +181,6 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {3D3DE8B3-6B54-4CF4-82B0-718E0009A4E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3D3DE8B3-6B54-4CF4-82B0-718E0009A4E5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3D3DE8B3-6B54-4CF4-82B0-718E0009A4E5}.Debug|x64.ActiveCfg = Debug|Any CPU - {3D3DE8B3-6B54-4CF4-82B0-718E0009A4E5}.Debug|x64.Build.0 = Debug|Any CPU - {3D3DE8B3-6B54-4CF4-82B0-718E0009A4E5}.Debug|x86.ActiveCfg = Debug|Any CPU - {3D3DE8B3-6B54-4CF4-82B0-718E0009A4E5}.Debug|x86.Build.0 = Debug|Any CPU - {3D3DE8B3-6B54-4CF4-82B0-718E0009A4E5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3D3DE8B3-6B54-4CF4-82B0-718E0009A4E5}.Release|Any CPU.Build.0 = Release|Any CPU - {3D3DE8B3-6B54-4CF4-82B0-718E0009A4E5}.Release|x64.ActiveCfg = Release|Any CPU - {3D3DE8B3-6B54-4CF4-82B0-718E0009A4E5}.Release|x64.Build.0 = Release|Any CPU - {3D3DE8B3-6B54-4CF4-82B0-718E0009A4E5}.Release|x86.ActiveCfg = Release|Any CPU - {3D3DE8B3-6B54-4CF4-82B0-718E0009A4E5}.Release|x86.Build.0 = Release|Any CPU - {96251C41-7953-46DC-B131-5A070640959A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {96251C41-7953-46DC-B131-5A070640959A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {96251C41-7953-46DC-B131-5A070640959A}.Debug|x64.ActiveCfg = Debug|Any CPU - {96251C41-7953-46DC-B131-5A070640959A}.Debug|x64.Build.0 = Debug|Any CPU - {96251C41-7953-46DC-B131-5A070640959A}.Debug|x86.ActiveCfg = Debug|Any CPU - {96251C41-7953-46DC-B131-5A070640959A}.Debug|x86.Build.0 = Debug|Any CPU - {96251C41-7953-46DC-B131-5A070640959A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {96251C41-7953-46DC-B131-5A070640959A}.Release|Any CPU.Build.0 = Release|Any CPU - {96251C41-7953-46DC-B131-5A070640959A}.Release|x64.ActiveCfg = Release|Any CPU - {96251C41-7953-46DC-B131-5A070640959A}.Release|x64.Build.0 = Release|Any CPU - {96251C41-7953-46DC-B131-5A070640959A}.Release|x86.ActiveCfg = Release|Any CPU - {96251C41-7953-46DC-B131-5A070640959A}.Release|x86.Build.0 = Release|Any CPU - {92F0615B-4C8F-456C-86C0-39384BB7031E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {92F0615B-4C8F-456C-86C0-39384BB7031E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {92F0615B-4C8F-456C-86C0-39384BB7031E}.Debug|x64.ActiveCfg = Debug|Any CPU - {92F0615B-4C8F-456C-86C0-39384BB7031E}.Debug|x64.Build.0 = Debug|Any CPU - {92F0615B-4C8F-456C-86C0-39384BB7031E}.Debug|x86.ActiveCfg = Debug|Any CPU - {92F0615B-4C8F-456C-86C0-39384BB7031E}.Debug|x86.Build.0 = Debug|Any CPU - {92F0615B-4C8F-456C-86C0-39384BB7031E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {92F0615B-4C8F-456C-86C0-39384BB7031E}.Release|Any CPU.Build.0 = Release|Any CPU - {92F0615B-4C8F-456C-86C0-39384BB7031E}.Release|x64.ActiveCfg = Release|Any CPU - {92F0615B-4C8F-456C-86C0-39384BB7031E}.Release|x64.Build.0 = Release|Any CPU - {92F0615B-4C8F-456C-86C0-39384BB7031E}.Release|x86.ActiveCfg = Release|Any CPU - {92F0615B-4C8F-456C-86C0-39384BB7031E}.Release|x86.Build.0 = Release|Any CPU - {BC03F087-9B6F-4A66-9571-B0C5C204A101}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BC03F087-9B6F-4A66-9571-B0C5C204A101}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BC03F087-9B6F-4A66-9571-B0C5C204A101}.Debug|x64.ActiveCfg = Debug|Any CPU - {BC03F087-9B6F-4A66-9571-B0C5C204A101}.Debug|x64.Build.0 = Debug|Any CPU - {BC03F087-9B6F-4A66-9571-B0C5C204A101}.Debug|x86.ActiveCfg = Debug|Any CPU - {BC03F087-9B6F-4A66-9571-B0C5C204A101}.Debug|x86.Build.0 = Debug|Any CPU - {BC03F087-9B6F-4A66-9571-B0C5C204A101}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BC03F087-9B6F-4A66-9571-B0C5C204A101}.Release|Any CPU.Build.0 = Release|Any CPU - {BC03F087-9B6F-4A66-9571-B0C5C204A101}.Release|x64.ActiveCfg = Release|Any CPU - {BC03F087-9B6F-4A66-9571-B0C5C204A101}.Release|x64.Build.0 = Release|Any CPU - {BC03F087-9B6F-4A66-9571-B0C5C204A101}.Release|x86.ActiveCfg = Release|Any CPU - {BC03F087-9B6F-4A66-9571-B0C5C204A101}.Release|x86.Build.0 = Release|Any CPU {AF371A60-8A85-4ADF-BE44-0F2B94234DB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AF371A60-8A85-4ADF-BE44-0F2B94234DB1}.Debug|Any CPU.Build.0 = Debug|Any CPU {AF371A60-8A85-4ADF-BE44-0F2B94234DB1}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1209,18 +1153,6 @@ Global {6012D544-32B4-4F5C-B335-A224AA4F661D}.Release|x64.Build.0 = Release|Any CPU {6012D544-32B4-4F5C-B335-A224AA4F661D}.Release|x86.ActiveCfg = Release|Any CPU {6012D544-32B4-4F5C-B335-A224AA4F661D}.Release|x86.Build.0 = Release|Any CPU - {706DBAF7-0BB4-4B4F-9B44-E7C3372ECDF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {706DBAF7-0BB4-4B4F-9B44-E7C3372ECDF2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {706DBAF7-0BB4-4B4F-9B44-E7C3372ECDF2}.Debug|x64.ActiveCfg = Debug|Any CPU - {706DBAF7-0BB4-4B4F-9B44-E7C3372ECDF2}.Debug|x64.Build.0 = Debug|Any CPU - {706DBAF7-0BB4-4B4F-9B44-E7C3372ECDF2}.Debug|x86.ActiveCfg = Debug|Any CPU - {706DBAF7-0BB4-4B4F-9B44-E7C3372ECDF2}.Debug|x86.Build.0 = Debug|Any CPU - {706DBAF7-0BB4-4B4F-9B44-E7C3372ECDF2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {706DBAF7-0BB4-4B4F-9B44-E7C3372ECDF2}.Release|Any CPU.Build.0 = Release|Any CPU - {706DBAF7-0BB4-4B4F-9B44-E7C3372ECDF2}.Release|x64.ActiveCfg = Release|Any CPU - {706DBAF7-0BB4-4B4F-9B44-E7C3372ECDF2}.Release|x64.Build.0 = Release|Any CPU - {706DBAF7-0BB4-4B4F-9B44-E7C3372ECDF2}.Release|x86.ActiveCfg = Release|Any CPU - {706DBAF7-0BB4-4B4F-9B44-E7C3372ECDF2}.Release|x86.Build.0 = Release|Any CPU {06D0D7B2-EDA3-45A2-A060-AB791ED1DB80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {06D0D7B2-EDA3-45A2-A060-AB791ED1DB80}.Debug|Any CPU.Build.0 = Debug|Any CPU {06D0D7B2-EDA3-45A2-A060-AB791ED1DB80}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1233,18 +1165,18 @@ Global {06D0D7B2-EDA3-45A2-A060-AB791ED1DB80}.Release|x64.Build.0 = Release|Any CPU {06D0D7B2-EDA3-45A2-A060-AB791ED1DB80}.Release|x86.ActiveCfg = Release|Any CPU {06D0D7B2-EDA3-45A2-A060-AB791ED1DB80}.Release|x86.Build.0 = Release|Any CPU - {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Debug|x64.ActiveCfg = Debug|Any CPU - {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Debug|x64.Build.0 = Debug|Any CPU - {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Debug|x86.ActiveCfg = Debug|Any CPU - {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Debug|x86.Build.0 = Debug|Any CPU - {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Release|Any CPU.Build.0 = Release|Any CPU - {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Release|x64.ActiveCfg = Release|Any CPU - {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Release|x64.Build.0 = Release|Any CPU - {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Release|x86.ActiveCfg = Release|Any CPU - {C6D371A8-D8EA-4CF4-844E-8C6DFDF61642}.Release|x86.Build.0 = Release|Any CPU + {F2870943-14EA-4AD2-AC01-E4E66E0B63F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2870943-14EA-4AD2-AC01-E4E66E0B63F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2870943-14EA-4AD2-AC01-E4E66E0B63F4}.Debug|x64.ActiveCfg = Debug|Any CPU + {F2870943-14EA-4AD2-AC01-E4E66E0B63F4}.Debug|x64.Build.0 = Debug|Any CPU + {F2870943-14EA-4AD2-AC01-E4E66E0B63F4}.Debug|x86.ActiveCfg = Debug|Any CPU + {F2870943-14EA-4AD2-AC01-E4E66E0B63F4}.Debug|x86.Build.0 = Debug|Any CPU + {F2870943-14EA-4AD2-AC01-E4E66E0B63F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2870943-14EA-4AD2-AC01-E4E66E0B63F4}.Release|Any CPU.Build.0 = Release|Any CPU + {F2870943-14EA-4AD2-AC01-E4E66E0B63F4}.Release|x64.ActiveCfg = Release|Any CPU + {F2870943-14EA-4AD2-AC01-E4E66E0B63F4}.Release|x64.Build.0 = Release|Any CPU + {F2870943-14EA-4AD2-AC01-E4E66E0B63F4}.Release|x86.ActiveCfg = Release|Any CPU + {F2870943-14EA-4AD2-AC01-E4E66E0B63F4}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1330,7 +1262,6 @@ Global {EEB670DC-9FE2-45A2-B7E9-9BCF7D1056E4} = {D64F966A-B33B-4554-BA8C-A1AF91265996} {81B96508-D920-45F6-9534-0D348B11DFAB} = {D64F966A-B33B-4554-BA8C-A1AF91265996} {6012D544-32B4-4F5C-B335-A224AA4F661D} = {D64F966A-B33B-4554-BA8C-A1AF91265996} - {706DBAF7-0BB4-4B4F-9B44-E7C3372ECDF2} = {D64F966A-B33B-4554-BA8C-A1AF91265996} {06D0D7B2-EDA3-45A2-A060-AB791ED1DB80} = {D64F966A-B33B-4554-BA8C-A1AF91265996} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/src/ProjectTemplates/test/BaselineTest.cs b/src/ProjectTemplates/test/BaselineTest.cs index b6f63f9f384a587a4fa35cafc84a08c9a825788d..8382a22cbdce47e9aec29de92929ac2ce25dd4f0 100644 --- a/src/ProjectTemplates/test/BaselineTest.cs +++ b/src/ProjectTemplates/test/BaselineTest.cs @@ -90,7 +90,13 @@ namespace Templates.Test relativePath.EndsWith(".props", StringComparison.Ordinal) || relativePath.EndsWith(".targets", StringComparison.Ordinal) || relativePath.StartsWith("bin/", StringComparison.Ordinal) || - relativePath.StartsWith("obj/", StringComparison.Ordinal)) + relativePath.StartsWith("obj/", StringComparison.Ordinal) || + relativePath.EndsWith(".sln", StringComparison.Ordinal) || + relativePath.EndsWith(".targets", StringComparison.Ordinal) || + relativePath.StartsWith("bin/", StringComparison.Ordinal) || + relativePath.StartsWith("obj/", StringComparison.Ordinal) || + relativePath.Contains("/bin/", StringComparison.Ordinal) || + relativePath.Contains("/obj/", StringComparison.Ordinal)) { continue; } diff --git a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs index 41cb82ac21ec2a38987876bf8392f37d887d344d..7b599785a86f8d953271f37f979852ff7e871789 100644 --- a/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs +++ b/src/ProjectTemplates/test/BlazorWasmTemplateTest.cs @@ -1,13 +1,21 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.IO; +using System.Linq; using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text.Json; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.E2ETesting; using Microsoft.Extensions.CommandLineUtils; +using Newtonsoft.Json.Linq; using OpenQA.Selenium; +using OpenQA.Selenium.Support.Extensions; using Templates.Test.Helpers; using Xunit; using Xunit.Abstractions; @@ -24,6 +32,11 @@ namespace Templates.Test public ProjectFactoryFixture ProjectFactory { get; set; } + public override Task InitializeAsync() + { + return InitializeAsync(isolationContext: Guid.NewGuid().ToString()); + } + [Fact] public async Task BlazorWasmStandaloneTemplate_Works() { @@ -36,22 +49,16 @@ namespace Templates.Test var publishResult = await project.RunDotNetPublishAsync(); Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", project, publishResult)); + // The service worker assets manifest isn't generated for non-PWA projects + var publishDir = Path.Combine(project.TemplatePublishDir, "wwwroot"); + Assert.False(File.Exists(Path.Combine(publishDir, "service-worker-assets.js")), "Non-PWA templates should not produce service-worker-assets.js"); + var buildResult = await project.RunDotNetBuildAsync(); Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", project, buildResult)); await BuildAndRunTest(project.ProjectName, project); - var publishDir = Path.Combine(project.TemplatePublishDir, project.ProjectName, "dist"); - AspNetProcess.EnsureDevelopmentCertificates(); - - Output.WriteLine("Running dotnet serve on published output..."); - using var serveProcess = ProcessEx.Run(Output, publishDir, DotNetMuxer.MuxerPathOrDefault(), "serve -S"); - - // Todo: Use dynamic port assignment: https://github.com/natemcmaster/dotnet-serve/pull/40/files - var listeningUri = "https://localhost:8080"; - Output.WriteLine($"Opening browser at {listeningUri}..."); - Browser.Navigate().GoToUrl(listeningUri); - TestBasicNavigation(project.ProjectName); + using var serveProcess = RunPublishedStandaloneBlazorProject(project); } [Fact] @@ -79,6 +86,7 @@ namespace Templates.Test ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", serverProject, aspNetProcess.Process)); await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html"); + await AssertCompressionFormat(aspNetProcess, "br"); if (BrowserFixture.IsHostAutomationSupported()) { aspNetProcess.VisitInBrowser(Browser); @@ -90,7 +98,308 @@ namespace Templates.Test } } - protected async Task BuildAndRunTest(string appName, Project project) + private static async Task AssertCompressionFormat(AspNetProcess aspNetProcess, string expectedEncoding) + { + var response = await aspNetProcess.SendRequest(() => + { + var request = new HttpRequestMessage(HttpMethod.Get, new Uri(aspNetProcess.ListeningUri, "/_framework/blazor.boot.json")); + // These are the same as chrome + request.Headers.AcceptEncoding.Clear(); + request.Headers.AcceptEncoding.Add(StringWithQualityHeaderValue.Parse("gzip")); + request.Headers.AcceptEncoding.Add(StringWithQualityHeaderValue.Parse("deflate")); + request.Headers.AcceptEncoding.Add(StringWithQualityHeaderValue.Parse("br")); + + return request; + }); + Assert.Equal(expectedEncoding, response.Content.Headers.ContentEncoding.Single()); + } + + [Fact] + public async Task BlazorWasmStandalonePwaTemplate_Works() + { + var project = await ProjectFactory.GetOrCreateProject("blazorstandalonepwa", Output); + project.TargetFramework = "netstandard2.1"; + + var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { "--pwa" }); + Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); + + var publishResult = await project.RunDotNetPublishAsync(); + Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", project, publishResult)); + + var buildResult = await project.RunDotNetBuildAsync(); + Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", project, buildResult)); + + await BuildAndRunTest(project.ProjectName, project); + + ValidatePublishedServiceWorker(project); + + using (var serverProcess = RunPublishedStandaloneBlazorProject(project)) + { + // We want to use this form to ensure that it gets disposed right after the test. + } + + // Todo: Use dynamic port assignment: https://github.com/natemcmaster/dotnet-serve/pull/40/files + var listeningUri = "https://localhost:8080"; + + // The PWA template supports offline use. By now, the browser should have cached everything it needs, + // so we can continue working even without the server. + ValidateAppWorksOffline(project, listeningUri, skipFetchData: false); + } + + [Fact] + public async Task BlazorWasmHostedPwaTemplate_Works() + { + var project = await ProjectFactory.GetOrCreateProject("blazorhostedpwa", Output); + + var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { "--hosted", "--pwa" }); + Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); + + var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); + + var publishResult = await serverProject.RunDotNetPublishAsync(); + Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", serverProject, publishResult)); + + var buildResult = await serverProject.RunDotNetBuildAsync(); + Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", serverProject, buildResult)); + + await BuildAndRunTest(project.ProjectName, serverProject); + + ValidatePublishedServiceWorker(serverProject); + + string listeningUri; + using (var aspNetProcess = serverProject.StartPublishedProjectAsync()) + { + Assert.False( + aspNetProcess.Process.HasExited, + ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", serverProject, aspNetProcess.Process)); + + await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html"); + if (BrowserFixture.IsHostAutomationSupported()) + { + aspNetProcess.VisitInBrowser(Browser); + TestBasicNavigation(project.ProjectName); + } + else + { + BrowserFixture.EnforceSupportedConfigurations(); + } + + // Note: we don't want to use aspNetProcess.ListeningUri because that isn't necessarily the HTTPS URI + var browserUri = new Uri(Browser.Url); + listeningUri = $"{browserUri.Scheme}://{browserUri.Authority}"; + } + + // The PWA template supports offline use. By now, the browser should have cached everything it needs, + // so we can continue working even without the server. + // Since this is the hosted project, backend APIs won't work offline, so we need to skip "fetchdata" + ValidateAppWorksOffline(project, listeningUri, skipFetchData: true); + } + + private void ValidatePublishedServiceWorker(Project project) + { + var publishDir = Path.Combine(project.TemplatePublishDir, "wwwroot"); + + // When publishing the PWA template, we generate an assets manifest + // and move service-worker.published.js to overwrite service-worker.js + Assert.False(File.Exists(Path.Combine(publishDir, "service-worker.published.js")), "service-worker.published.js should not be published"); + Assert.True(File.Exists(Path.Combine(publishDir, "service-worker.js")), "service-worker.js should be published"); + Assert.True(File.Exists(Path.Combine(publishDir, "service-worker-assets.js")), "service-worker-assets.js should be published"); + + // We automatically append the SWAM version as a comment in the published service worker file + var serviceWorkerAssetsManifestContents = ReadFile(publishDir, "service-worker-assets.js"); + var serviceWorkerContents = ReadFile(publishDir, "service-worker.js"); + + // Parse the "version": "..." value from the SWAM, and check it's in the service worker + var serviceWorkerAssetsManifestVersionMatch = new Regex(@"^\s*\""version\"":\s*(\""[^\""]+\"")", RegexOptions.Multiline) + .Match(serviceWorkerAssetsManifestContents); + Assert.True(serviceWorkerAssetsManifestVersionMatch.Success); + var serviceWorkerAssetsManifestVersionJson = serviceWorkerAssetsManifestVersionMatch.Groups[1].Captures[0].Value; + var serviceWorkerAssetsManifestVersion = JsonSerializer.Deserialize<string>(serviceWorkerAssetsManifestVersionJson); + Assert.True(serviceWorkerContents.Contains($"/* Manifest version: {serviceWorkerAssetsManifestVersion} */", StringComparison.Ordinal)); + } + + private void ValidateAppWorksOffline(Project project, string listeningUri, bool skipFetchData) + { + Browser.Navigate().GoToUrl("about:blank"); // Be sure we're really reloading + Output.WriteLine($"Opening browser without corresponding server at {listeningUri}..."); + Browser.Navigate().GoToUrl(listeningUri); + TestBasicNavigation(project.ProjectName, skipFetchData: skipFetchData); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task BlazorWasmHostedTemplate_IndividualAuth_Works(bool useLocalDb) + { + var project = await ProjectFactory.GetOrCreateProject("blazorhostedindividual" + (useLocalDb ? "uld" : ""), Output); + + var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { "--hosted", "-au", "Individual", useLocalDb ? "-uld" : "" }); + Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); + + var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); + + var serverProjectFileContents = ReadFile(serverProject.TemplateOutputDir, $"{serverProject.ProjectName}.csproj"); + if (!useLocalDb) + { + Assert.Contains(".db", serverProjectFileContents); + } + + var appSettings = ReadFile(serverProject.TemplateOutputDir, "appSettings.json"); + var element = JsonSerializer.Deserialize<JsonElement>(appSettings); + var clientsProperty = element.GetProperty("IdentityServer").EnumerateObject().Single().Value.EnumerateObject().Single(); + var replacedSection = element.GetRawText().Replace(clientsProperty.Name, serverProject.ProjectName.Replace(".Server", ".Client")); + var appSettingsPath = Path.Combine(serverProject.TemplateOutputDir, "appSettings.json"); + File.WriteAllText(appSettingsPath, replacedSection); + + var publishResult = await serverProject.RunDotNetPublishAsync(); + Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", serverProject, publishResult)); + + // 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. + + var buildResult = await serverProject.RunDotNetBuildAsync(); + Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", serverProject, buildResult)); + + var migrationsResult = await serverProject.RunDotNetEfCreateMigrationAsync("blazorwasm"); + Assert.True(0 == migrationsResult.ExitCode, ErrorMessages.GetFailedProcessMessage("run EF migrations", serverProject, migrationsResult)); + serverProject.AssertEmptyMigration("blazorwasm"); + + if (useLocalDb) + { + using var dbUpdateResult = await serverProject.RunDotNetEfUpdateDatabaseAsync(); + Assert.True(0 == dbUpdateResult.ExitCode, ErrorMessages.GetFailedProcessMessage("update database", serverProject, dbUpdateResult)); + } + + await BuildAndRunTest(project.ProjectName, serverProject, usesAuth: true); + + UpdatePublishedSettings(serverProject); + + using var aspNetProcess = serverProject.StartPublishedProjectAsync(); + + Assert.False( + aspNetProcess.Process.HasExited, + ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", serverProject, aspNetProcess.Process)); + + await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html"); + if (BrowserFixture.IsHostAutomationSupported()) + { + aspNetProcess.VisitInBrowser(Browser); + TestBasicNavigation(project.ProjectName, usesAuth: true); + } + else + { + BrowserFixture.EnforceSupportedConfigurations(); + } + } + + [Fact] + public async Task BlazorWasmStandaloneTemplate_IndividualAuth_Works() + { + var project = await ProjectFactory.GetOrCreateProject("blazorstandaloneindividual", Output); + project.TargetFramework = "netstandard2.1"; + + var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { + "-au", + "Individual", + "--authority", + "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration", + "--client-id", + "sample-client-id" + }); + + Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); + + var publishResult = await project.RunDotNetPublishAsync(); + Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", project, publishResult)); + + // 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. + + var buildResult = await project.RunDotNetBuildAsync(); + Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", project, buildResult)); + + // We don't want to test the auth flow as we don't have the required settings to talk to a third-party IdP + // but we want to make sure that we are able to run the app without errors. + // That will at least test that we are able to initialize and retrieve the configuration from the IdP + // for that, we use the common microsoft tenant. + await BuildAndRunTest(project.ProjectName, project, usesAuth: false); + + using var serveProcess = RunPublishedStandaloneBlazorProject(project); + } + + public static TheoryData<TemplateInstance> TemplateData => new TheoryData<TemplateInstance> + { + new TemplateInstance( + "blazorwasmhostedaadb2c", "-ho", + "-au", "IndividualB2C", + "--aad-b2c-instance", "example.b2clogin.com", + "-ssp", "b2c_1_siupin", + "--client-id", "clientId", + "--domain", "my-domain", + "--default-scope", "full", + "--app-id-uri", "ApiUri", + "--api-client-id", "1234123413241324"), + new TemplateInstance( + "blazorwasmhostedaad", "-ho", + "-au", "SingleOrg", + "--domain", "my-domain", + "--tenant-id", "tenantId", + "--client-id", "clientId", + "--default-scope", "full", + "--app-id-uri", "ApiUri", + "--api-client-id", "1234123413241324"), + new TemplateInstance( + "blazorwasmstandaloneaadb2c", + "-au", "IndividualB2C", + "--aad-b2c-instance", "example.b2clogin.com", + "-ssp", "b2c_1_siupin", + "--client-id", "clientId", + "--domain", "my-domain"), + new TemplateInstance( + "blazorwasmstandaloneaad", + "-au", "SingleOrg", + "--domain", "my-domain", + "--tenant-id", "tenantId", + "--client-id", "clientId"), + }; + + public class TemplateInstance + { + public TemplateInstance(string name, params string[] arguments) + { + Name = name; + Arguments = arguments; + } + + public string Name { get; } + public string[] Arguments { get; } + } + + [Theory] + [MemberData(nameof(TemplateData))] + public async Task BlazorWasmHostedTemplate_AzureActiveDirectoryTemplate_Works(TemplateInstance instance) + { + var project = await ProjectFactory.GetOrCreateProject(instance.Name, Output); + project.TargetFramework = "netstandard2.1"; + + var createResult = await project.RunDotNetNewAsync("blazorwasm", args: instance.Arguments); + + Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); + + var publishResult = await project.RunDotNetPublishAsync(); + Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", project, publishResult)); + + // 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. + + var buildResult = await project.RunDotNetBuildAsync(); + Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", project, buildResult)); + } + + protected async Task BuildAndRunTest(string appName, Project project, bool usesAuth = false) { using var aspNetProcess = project.StartBuiltProjectAsync(); @@ -99,10 +408,12 @@ namespace Templates.Test ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", project, aspNetProcess.Process)); await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html"); + // We only do brotli precompression for published apps + await AssertCompressionFormat(aspNetProcess, "gzip"); if (BrowserFixture.IsHostAutomationSupported()) { aspNetProcess.VisitInBrowser(Browser); - TestBasicNavigation(appName); + TestBasicNavigation(appName, usesAuth); } else { @@ -110,8 +421,17 @@ namespace Templates.Test } } - private void TestBasicNavigation(string appName) + private void TestBasicNavigation(string appName, bool usesAuth = false, bool skipFetchData = false) { + // Start fresh always + if (usesAuth) + { + Browser.ExecuteJavaScript("sessionStorage.clear()"); + Browser.ExecuteJavaScript("localStorage.clear()"); + Browser.Manage().Cookies.DeleteAllCookies(); + Browser.Navigate().Refresh(); + } + // Give components.server enough time to load so that it can replace // the prerendered content before we start making assertions. Thread.Sleep(5000); @@ -133,14 +453,60 @@ namespace Templates.Test Browser.FindElement(By.CssSelector("p+button")).Click(); Browser.Equal("Current count: 1", () => Browser.FindElement(By.CssSelector("h1 + p")).Text); - // Can navigate to the 'fetch data' page - Browser.FindElement(By.PartialLinkText("Fetch data")).Click(); - Browser.Contains("fetchdata", () => Browser.Url); - Browser.Equal("Weather forecast", () => Browser.FindElement(By.TagName("h1")).Text); + if (usesAuth) + { + Browser.FindElement(By.PartialLinkText("Log in")).Click(); + Browser.Contains("/Identity/Account/Login", () => Browser.Url); + + Browser.FindElement(By.PartialLinkText("Register as a new user")).Click(); + + var userName = $"{Guid.NewGuid()}@example.com"; + var password = $"!Test.Password1$"; + Browser.Exists(By.Name("Input.Email")); + Browser.FindElement(By.Name("Input.Email")).SendKeys(userName); + Browser.FindElement(By.Name("Input.Password")).SendKeys(password); + Browser.FindElement(By.Name("Input.ConfirmPassword")).SendKeys(password); + Browser.FindElement(By.Id("registerSubmit")).Click(); + + // We will be redirected to the RegisterConfirmation + Browser.Contains("/Identity/Account/RegisterConfirmation", () => Browser.Url); + Browser.FindElement(By.PartialLinkText("Click here to confirm your account")).Click(); + + // We will be redirected to the ConfirmEmail + Browser.Contains("/Identity/Account/ConfirmEmail", () => Browser.Url); + + // Now we can login + Browser.FindElement(By.PartialLinkText("Login")).Click(); + Browser.Exists(By.Name("Input.Email")); + Browser.FindElement(By.Name("Input.Email")).SendKeys(userName); + Browser.FindElement(By.Name("Input.Password")).SendKeys(password); + Browser.FindElement(By.Id("login-submit")).Click(); + + // Need to navigate to fetch page + Browser.Navigate().GoToUrl(new Uri(Browser.Url).GetLeftPart(UriPartial.Authority)); + Browser.Equal(appName.Trim(), () => Browser.Title.Trim()); + } - // Asynchronously loads and displays the table of weather forecasts - Browser.Exists(By.CssSelector("table>tbody>tr")); - Browser.Equal(5, () => Browser.FindElements(By.CssSelector("p+table>tbody>tr")).Count); + if (!skipFetchData) + { + // Can navigate to the 'fetch data' page + Browser.FindElement(By.PartialLinkText("Fetch data")).Click(); + Browser.Contains("fetchdata", () => Browser.Url); + Browser.Equal("Weather forecast", () => Browser.FindElement(By.TagName("h1")).Text); + + // Asynchronously loads and displays the table of weather forecasts + Browser.Exists(By.CssSelector("table>tbody>tr")); + Browser.Equal(5, () => Browser.FindElements(By.CssSelector("p+table>tbody>tr")).Count); + } + } + + 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 Project GetSubProject(Project project, string projectDirectory, string projectName) @@ -163,5 +529,42 @@ namespace Templates.Test return subProject; } + + private void UpdatePublishedSettings(Project serverProject) + { + // Hijack here the config file to use the development key during publish. + var appSettings = JObject.Parse(File.ReadAllText(Path.Combine(serverProject.TemplateOutputDir, "appsettings.json"))); + var appSettingsDevelopment = JObject.Parse(File.ReadAllText(Path.Combine(serverProject.TemplateOutputDir, "appsettings.Development.json"))); + ((JObject)appSettings["IdentityServer"]).Merge(appSettingsDevelopment["IdentityServer"]); + ((JObject)appSettings["IdentityServer"]).Merge(new + { + IdentityServer = new + { + Key = new + { + FilePath = "./tempkey.json" + } + } + }); + var testAppSettings = appSettings.ToString(); + File.WriteAllText(Path.Combine(serverProject.TemplatePublishDir, "appsettings.json"), testAppSettings); + } + + + private ProcessEx RunPublishedStandaloneBlazorProject(Project project) + { + var publishDir = Path.Combine(project.TemplatePublishDir, "wwwroot"); + AspNetProcess.EnsureDevelopmentCertificates(); + + Output.WriteLine("Running dotnet serve on published output..."); + var serveProcess = ProcessEx.Run(Output, publishDir, DotNetMuxer.MuxerPathOrDefault(), "serve -S"); + + // Todo: Use dynamic port assignment: https://github.com/natemcmaster/dotnet-serve/pull/40/files + var listeningUri = "https://localhost:8080"; + Output.WriteLine($"Opening browser at {listeningUri}..."); + Browser.Navigate().GoToUrl(listeningUri); + TestBasicNavigation(project.ProjectName); + return serveProcess; + } } } diff --git a/src/ProjectTemplates/test/ByteOrderMarkTest.cs b/src/ProjectTemplates/test/ByteOrderMarkTest.cs index df7acd2f4ddd6ef9ca817ef1548a61b4cdcc94bd..01ed7e4dacea3e197a607ebe4278ba7bb25671ca 100644 --- a/src/ProjectTemplates/test/ByteOrderMarkTest.cs +++ b/src/ProjectTemplates/test/ByteOrderMarkTest.cs @@ -20,6 +20,7 @@ namespace Templates.Test } [Theory] + [InlineData("ComponentsWebAssembly.ProjectTemplates")] [InlineData("Web.ProjectTemplates")] [InlineData("Web.Spa.ProjectTemplates")] public void JSAndJSONInAllTemplates_ShouldNotContainBOM(string projectName) diff --git a/src/ProjectTemplates/test/Helpers/AspNetProcess.cs b/src/ProjectTemplates/test/Helpers/AspNetProcess.cs index 753eb1258a521708fd1d1ca28ab47d0458411630..97cec926b8c382b74bbca51e02e023e094f774d5 100644 --- a/src/ProjectTemplates/test/Helpers/AspNetProcess.cs +++ b/src/ProjectTemplates/test/Helpers/AspNetProcess.cs @@ -240,9 +240,16 @@ namespace Templates.Test.Helpers return RequestWithRetries(client => client.GetAsync(new Uri(ListeningUri, path)), _httpClient); } + internal Task<HttpResponseMessage> SendRequest(Func<HttpRequestMessage> requestFactory) + { + return RequestWithRetries(client => client.SendAsync(requestFactory()), _httpClient); + } + + public async Task AssertStatusCode(string requestUrl, HttpStatusCode statusCode, string acceptContentType = null) { - var response = await RequestWithRetries(client => { + var response = await RequestWithRetries(client => + { var request = new HttpRequestMessage( HttpMethod.Get, new Uri(ListeningUri, requestUrl)); diff --git a/src/ProjectTemplates/test/Helpers/Project.cs b/src/ProjectTemplates/test/Helpers/Project.cs index 19f1d46b7f9599dddef34d8be9fa3f2a4de84296..10dd191bc200f8b181efe162419034969e7e4771 100644 --- a/src/ProjectTemplates/test/Helpers/Project.cs +++ b/src/ProjectTemplates/test/Helpers/Project.cs @@ -120,7 +120,7 @@ namespace Templates.Test.Helpers await effectiveLock.WaitAsync(); try { - var result = ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), $"publish -c Release /bl /nr:false {additionalArgs}", packageOptions); + var result = ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), $"publish -c Release /bl:publish.binlog /nr:false {additionalArgs}", packageOptions); await result.Exited; CaptureBinLogOnFailure(result); return result; @@ -515,10 +515,20 @@ namespace Templates.Test.Helpers { if (result.ExitCode != 0 && !string.IsNullOrEmpty(ArtifactsLogDir)) { - var sourceFile = Path.Combine(TemplateOutputDir, "msbuild.binlog"); - Assert.True(File.Exists(sourceFile), $"Log for '{ProjectName}' not found in '{sourceFile}'."); - var destination = Path.Combine(ArtifactsLogDir, ProjectName + ".binlog"); - File.Move(sourceFile, destination); + var build = Path.Combine(TemplateOutputDir, "msbuild.binlog"); + var publish = Path.Combine(TemplateOutputDir, "publish.binlog"); + Assert.True(File.Exists(build) || File.Exists(publish), $"Log for '{ProjectName}' not found in '{build}'."); + if (File.Exists(build)) + { + var destination = Path.Combine(ArtifactsLogDir, ProjectName + ".binlog"); + File.Move(build, destination); + } + + if (File.Exists(publish)) + { + var destination = Path.Combine(ArtifactsLogDir, ProjectName + ".publish.binlog"); + File.Move(publish, destination); + } } } diff --git a/src/ProjectTemplates/test/Helpers/ProjectFactoryFixture.cs b/src/ProjectTemplates/test/Helpers/ProjectFactoryFixture.cs index ffd1b0ae4ffcd852ee85f6dce8fdfce3b09d6c71..4dbb65cf8c393fd85b8ab64c194cae0b6523f3b2 100644 --- a/src/ProjectTemplates/test/Helpers/ProjectFactoryFixture.cs +++ b/src/ProjectTemplates/test/Helpers/ProjectFactoryFixture.cs @@ -48,7 +48,7 @@ namespace Templates.Test.Helpers NodeLock = NodeLock, Output = outputHelper, DiagnosticsMessageSink = DiagnosticsMessageSink, - ProjectGuid = Path.GetRandomFileName().Replace(".", string.Empty) + ProjectGuid = Path.GetRandomFileName().Replace(".", string.Empty).Replace("-", "").Replace("_", "") }; project.ProjectName = $"AspNet.{key}.{project.ProjectGuid}"; diff --git a/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs b/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs index 5d6985f691d8febfda248810d85e8836691402cf..cac39bdb7b70bfc4a850173783b59722d2680dc4 100644 --- a/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs +++ b/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs @@ -37,7 +37,7 @@ namespace Templates.Test.Helpers "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.0", "Microsoft.DotNet.Web.Spa.ProjectTemplates.3.1", "Microsoft.DotNet.Web.Spa.ProjectTemplates", - "Microsoft.AspNetCore.Blazor.Templates", + "Microsoft.AspNetCore.Components.WebAssembly.Templates", }; public static string CustomHivePath { get; } = typeof(TemplatePackageInstaller) diff --git a/src/ProjectTemplates/test/Infrastructure/TemplateTests.props.in b/src/ProjectTemplates/test/Infrastructure/TemplateTests.props.in index 25fbc524a47e6f398e933e15d2781d92294f0f7b..103ccbeafc8dba63f9e94c1d89c2cf22859f6a9e 100644 --- a/src/ProjectTemplates/test/Infrastructure/TemplateTests.props.in +++ b/src/ProjectTemplates/test/Infrastructure/TemplateTests.props.in @@ -40,6 +40,14 @@ IsImplicitlyDefined="true" /> </ItemGroup> + <!-- Temporarily remove the components analyzer due to build issues --> + <Target Name="RemoveComponentsAnalyzer" BeforeTargets="CoreCompile"> + <ItemGroup> + <_AnalyzerToRemove Include="@(Analyzer)" Condition="'%(Analyzer.NuGetPackageId)' == 'Microsoft.AspNetCore.Components.Analyzers'" /> + <Analyzer Remove="@(_AnalyzerToRemove)" /> + </ItemGroup> + </Target> + <ItemGroup Condition="'$(UsingMicrosoftNETSdkWeb)' == 'true' OR '$(RazorSdkCurrentVersionProps)' != ''"> <!-- Use the Razor SDK as a package reference. The version of the .NET Core SDK we build with often contains a version of the Razor SDK diff --git a/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj b/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj index bc280ce2a3d7bde22efd743da392c7cdb1c39c13..e34edfb772141a3284d160bb0d6db7a2acb49d80 100644 --- a/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj +++ b/src/ProjectTemplates/test/ProjectTemplates.Tests.csproj @@ -37,6 +37,7 @@ </ItemGroup> <ItemGroup> + <ProjectReference Include="../ComponentsWebAssembly.ProjectTemplates/Microsoft.AspNetCore.Components.WebAssembly.Templates.csproj" ReferenceOutputAssembly="false" /> <ProjectReference Include="$(RepoRoot)src\Framework\src\Microsoft.AspNetCore.App.Runtime.csproj"> <ReferenceOutputAssembly>false</ReferenceOutputAssembly> <SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties> @@ -47,7 +48,6 @@ <ProjectReference Include="../Web.ItemTemplates/Microsoft.DotNet.Web.ItemTemplates.csproj" ReferenceOutputAssembly="false" /> <ProjectReference Include="../Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj" ReferenceOutputAssembly="false" /> <ProjectReference Include="../Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj" ReferenceOutputAssembly="false" /> - <ProjectReference Include="../BlazorWasm.ProjectTemplates/Microsoft.AspNetCore.Blazor.Templates.csproj" ReferenceOutputAssembly="false" /> </ItemGroup> <ItemGroup> diff --git a/src/Shared/CommandLineUtils/Utilities/DotNetMuxer.cs b/src/Shared/CommandLineUtils/Utilities/DotNetMuxer.cs new file mode 100644 index 0000000000000000000000000000000000000000..52c98b5eb251f41328b613b489a5318e74d7b186 --- /dev/null +++ b/src/Shared/CommandLineUtils/Utilities/DotNetMuxer.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +// System.AppContext.GetData is not available in these frameworks +#if !NET451 && !NET452 && !NET46 && !NET461 + +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace Microsoft.Extensions.CommandLineUtils +{ + /// <summary> + /// Utilities for finding the "dotnet.exe" file from the currently running .NET Core application + /// </summary> + internal static class DotNetMuxer + { + private const string MuxerName = "dotnet"; + + static DotNetMuxer() + { + MuxerPath = TryFindMuxerPath(); + } + + /// <summary> + /// The full filepath to the .NET Core muxer. + /// </summary> + public static string MuxerPath { get; } + + /// <summary> + /// Finds the full filepath to the .NET Core muxer, + /// or returns a string containing the default name of the .NET Core muxer ('dotnet'). + /// </summary> + /// <returns>The path or a string named 'dotnet'.</returns> + public static string MuxerPathOrDefault() + => MuxerPath ?? MuxerName; + + private static string TryFindMuxerPath() + { + var fileName = MuxerName; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + fileName += ".exe"; + } + + var mainModule = Process.GetCurrentProcess().MainModule; + if (!string.IsNullOrEmpty(mainModule?.FileName) + && Path.GetFileName(mainModule.FileName).Equals(fileName, StringComparison.OrdinalIgnoreCase)) + { + return mainModule.FileName; + } + + return null; + } + } +} +#endif diff --git a/src/Shared/E2ETesting/BrowserFixture.cs b/src/Shared/E2ETesting/BrowserFixture.cs index bf8b31d29426246cba07b46833ef108c44b2f266..4ccc5f5aca19dff9a3364628affb1a15f549255c 100644 --- a/src/Shared/E2ETesting/BrowserFixture.cs +++ b/src/Shared/E2ETesting/BrowserFixture.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Concurrent; using System.Diagnostics; +using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; @@ -68,6 +69,41 @@ namespace Microsoft.AspNetCore.E2ETesting { browser.Dispose(); } + + await DeleteBrowserUserProfileDirectoriesAsync(); + } + + private async Task DeleteBrowserUserProfileDirectoriesAsync() + { + foreach (var context in _browsers.Keys) + { + var userProfileDirectory = UserProfileDirectory(context); + if (!string.IsNullOrEmpty(userProfileDirectory) && Directory.Exists(userProfileDirectory)) + { + var attemptCount = 0; + while (true) + { + try + { + Directory.Delete(userProfileDirectory, recursive: true); + break; + } + catch (UnauthorizedAccessException ex) + { + attemptCount++; + if (attemptCount < 5) + { + Console.WriteLine($"Failed to delete browser profile directory '{userProfileDirectory}': '{ex}'. Will retry."); + await Task.Delay(2000); + } + else + { + throw; + } + } + } + } + } } public Task<(IWebDriver, ILogs)> GetOrCreateBrowserAsync(ITestOutputHelper output, string isolationContext = "") @@ -116,6 +152,13 @@ namespace Microsoft.AspNetCore.E2ETesting output.WriteLine($"Set {nameof(ChromeOptions)}.{nameof(opts.BinaryLocation)} to {binaryLocation}"); } + var userProfileDirectory = UserProfileDirectory(context); + if (!string.IsNullOrEmpty(userProfileDirectory)) + { + Directory.CreateDirectory(userProfileDirectory); + opts.AddArgument($"--user-data-dir={userProfileDirectory}"); + } + var instance = await SeleniumStandaloneServer.GetInstanceAsync(output); var attempt = 0; @@ -155,6 +198,16 @@ namespace Microsoft.AspNetCore.E2ETesting throw new InvalidOperationException("Couldn't create a Selenium remote driver client. The server is irresponsive"); } + private string UserProfileDirectory(string context) + { + if (string.IsNullOrEmpty(context)) + { + return null; + } + + return Path.Combine(Path.GetTempPath(), "BrowserFixtureUserProfiles", context); + } + private async Task<(IWebDriver browser, ILogs log)> CreateSauceBrowserAsync(string context, ITestOutputHelper output) { var sauce = E2ETestOptions.Instance.Sauce; diff --git a/src/Shared/E2ETesting/BrowserTestBase.cs b/src/Shared/E2ETesting/BrowserTestBase.cs index 5bf81134cb8d39d3e6ce7a0eab25c1954c082bcb..aea8b38727b718b6fdc3d9dd3955a38d804d3c5b 100644 --- a/src/Shared/E2ETesting/BrowserTestBase.cs +++ b/src/Shared/E2ETesting/BrowserTestBase.cs @@ -1,16 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using OpenQA.Selenium; -using OpenQA.Selenium.Support.UI; using Xunit; using Xunit.Abstractions; -using Xunit.Sdk; namespace Microsoft.AspNetCore.E2ETesting { @@ -21,11 +16,6 @@ namespace Microsoft.AspNetCore.E2ETesting private static readonly AsyncLocal<ILogs> _logs = new AsyncLocal<ILogs>(); private static readonly AsyncLocal<ITestOutputHelper> _output = new AsyncLocal<ITestOutputHelper>(); - // Limit the number of concurrent browser tests. - private readonly static int MaxConcurrentBrowsers = Environment.ProcessorCount * 2; - private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(MaxConcurrentBrowsers); - private bool _semaphoreHeld; - public BrowserTestBase(BrowserFixture browserFixture, ITestOutputHelper output) { BrowserFixture = browserFixture; @@ -44,11 +34,6 @@ namespace Microsoft.AspNetCore.E2ETesting public Task DisposeAsync() { - if (_semaphoreHeld) - { - _semaphore.Release(); - } - return Task.CompletedTask; } @@ -70,9 +55,6 @@ namespace Microsoft.AspNetCore.E2ETesting protected async Task InitializeBrowser(string isolationContext) { - await _semaphore.WaitAsync(TimeSpan.FromMinutes(30)); - _semaphoreHeld = true; - var (browser, logs) = await BrowserFixture.GetOrCreateBrowserAsync(Output, isolationContext); _asyncBrowser.Value = browser; _logs.Value = logs; diff --git a/src/Shared/E2ETesting/WaitAssert.cs b/src/Shared/E2ETesting/WaitAssert.cs index f6fee3e618fe14ee2476383ffe99ae5c41258164..9aa675bb9a4080234e6bf0439620210288dbee41 100644 --- a/src/Shared/E2ETesting/WaitAssert.cs +++ b/src/Shared/E2ETesting/WaitAssert.cs @@ -47,6 +47,9 @@ namespace Microsoft.AspNetCore.E2ETesting public static IWebElement Exists(this IWebDriver driver, By finder) => Exists(driver, finder, default); + public static TElement Exists<TElement>(this IWebDriver driver, Func<TElement> actual, TimeSpan timeout) + => WaitAssertCore(driver, actual, timeout); + public static void DoesNotExist(this IWebDriver driver, By finder, TimeSpan timeout = default) => WaitAssertCore(driver, () => { @@ -128,7 +131,7 @@ namespace Microsoft.AspNetCore.E2ETesting try { assertion(); - throw new InvalidOperationException("The assertion succeded after the timeout."); + throw new InvalidOperationException("The assertion succeeded after the timeout."); } catch (Exception ex) {