diff --git a/.github/actions/dotnet-sdk/action.yml b/.github/actions/dotnet-sdk/action.yml
index e53f87b5..8a0b68c6 100644
--- a/.github/actions/dotnet-sdk/action.yml
+++ b/.github/actions/dotnet-sdk/action.yml
@@ -14,7 +14,7 @@ runs:
with:
key: dotnet-sdk-windows-${{ inputs.UET_FRAMEWORK_TARGET }}
restore-keys: dotnet-sdk-windows-${{ inputs.UET_FRAMEWORK_TARGET }}
- path: .dotnet-${{ inputs.UET_FRAMEWORK_TARGET }}
+ path: .dotnet
- name: Cache .NET SDK (macOS)
if: ${{ runner.os == 'macOS' }}
id: cache-sdk-mac
@@ -22,64 +22,80 @@ runs:
with:
key: dotnet-sdk-mac-${{ inputs.UET_FRAMEWORK_TARGET }}
restore-keys: dotnet-sdk-mac-${{ inputs.UET_FRAMEWORK_TARGET }}
- path: .dotnet-${{ inputs.UET_FRAMEWORK_TARGET }}
+ path: .dotnet
+ - name: Cache .NET SDK (Linux)
+ if: ${{ runner.os == 'Linux' }}
+ id: cache-sdk-linux
+ uses: actions/cache@v4
+ with:
+ key: dotnet-sdk-linux-${{ inputs.UET_FRAMEWORK_TARGET }}
+ restore-keys: dotnet-sdk-linux-${{ inputs.UET_FRAMEWORK_TARGET }}
+ path: .dotnet
- name: Download .NET SDK (Windows)
if: ${{ runner.os == 'Windows' && steps.cache-sdk-win.outputs.cache-hit != 'true' }}
shell: pwsh
- env:
- UET_FRAMEWORK_TARGET: ${{ inputs.UET_FRAMEWORK_TARGET }}
- UET_DOTNET_WIN_DL: https://download.visualstudio.microsoft.com/download/pr/6902745c-34bd-4d66-8e84-d5b61a17dfb7/e61732b00f7e144e162d7e6914291f16/dotnet-sdk-8.0.101-win-x64.zip
run: |
- if (!(Test-Path .dotnet-${env:UET_FRAMEWORK_TARGET}\dotnet\dotnet-extracted)) {
- if (Test-Path ".dotnet-${env:UET_FRAMEWORK_TARGET}") {
- Remove-Item -Recurse -Force ".dotnet-${env:UET_FRAMEWORK_TARGET}"
+ if (!(Test-Path .dotnet\dotnet\dotnet-extracted)) {
+ if (Test-Path ".dotnet") {
+ Remove-Item -Recurse -Force ".dotnet"
}
Write-Host "Setting up .NET SDK..."
- New-Item -ItemType Directory ".dotnet-${env:UET_FRAMEWORK_TARGET}" | Out-Null
- curl.exe -L -o ".dotnet-${env:UET_FRAMEWORK_TARGET}\dotnet.zip" "${env:UET_DOTNET_WIN_DL}"
+ New-Item -ItemType Directory ".dotnet" | Out-Null
+ Invoke-WebRequest https://dot.net/v1/dotnet-install.ps1 -UseBasicParsing -OutFile ".dotnet\dotnet-install.ps1"
+ .\.dotnet\dotnet-install.ps1 -Channel STS -InstallDir ".dotnet\dotnet"
if ($LastExitCode -ne 0) {
exit $LastExitCode
}
- Expand-Archive -Path ".dotnet-${env:UET_FRAMEWORK_TARGET}\dotnet.zip" -DestinationPath ".dotnet-${env:UET_FRAMEWORK_TARGET}\dotnet" -Force | Out-Null
- Set-Content -Path .dotnet-${env:UET_FRAMEWORK_TARGET}\dotnet\dotnet-extracted -Value "done"
+ Set-Content -Path .dotnet\dotnet\dotnet-extracted -Value "done"
}
- name: Download .NET SDK (macOS)
if: ${{ runner.os == 'macOS' && steps.cache-sdk-mac.outputs.cache-hit != 'true' }}
shell: pwsh
- env:
- UET_FRAMEWORK_TARGET: ${{ inputs.UET_FRAMEWORK_TARGET }}
- UET_DOTNET_MAC_DL: https://download.visualstudio.microsoft.com/download/pr/ef083c06-7aee-4a4f-b18b-50c9a8990753/e206864e7910e81bbd9cb7e674ff1b4c/dotnet-sdk-8.0.101-osx-arm64.tar.gz
run: |
- if (!(Test-Path .dotnet-${env:UET_FRAMEWORK_TARGET}/dotnet/dotnet-extracted)) {
- if (Test-Path ".dotnet-${env:UET_FRAMEWORK_TARGET}") {
- Remove-Item -Recurse -Force ".dotnet-${env:UET_FRAMEWORK_TARGET}"
+ if (!(Test-Path .dotnet/dotnet/dotnet-extracted)) {
+ if (Test-Path ".dotnet") {
+ Remove-Item -Recurse -Force ".dotnet"
}
Write-Host "Setting up .NET SDK..."
- New-Item -ItemType Directory ".dotnet-${env:UET_FRAMEWORK_TARGET}" | Out-Null
- curl -L -o ".dotnet-${env:UET_FRAMEWORK_TARGET}/dotnet.tar.gz" "${env:UET_DOTNET_MAC_DL}"
+ New-Item -ItemType Directory ".dotnet" | Out-Null
+ Invoke-WebRequest https://dot.net/v1/dotnet-install.sh -UseBasicParsing -OutFile ".dotnet/dotnet-install.sh"
+ chmod a+x .dotnet/dotnet-install.sh
+ ./.dotnet/dotnet-install.sh --channel STS --install-dir ".dotnet/dotnet"
if ($LastExitCode -ne 0) {
exit $LastExitCode
}
- New-Item -ItemType Directory ".dotnet-${env:UET_FRAMEWORK_TARGET}/dotnet" | Out-Null
- Push-Location ".dotnet-${env:UET_FRAMEWORK_TARGET}/dotnet"
- try {
- tar -xvf "../dotnet.tar.gz"
- } finally {
- Pop-Location
+ Set-Content -Path .dotnet/dotnet/dotnet-extracted -Value "done"
+ }
+ - name: Download .NET SDK (Linux)
+ if: ${{ runner.os == 'Linux' && steps.cache-sdk-linux.outputs.cache-hit != 'true' }}
+ shell: pwsh
+ run: |
+ if (!(Test-Path .dotnet/dotnet/dotnet-extracted)) {
+ if (Test-Path ".dotnet") {
+ Remove-Item -Recurse -Force ".dotnet"
}
- Set-Content -Path .dotnet-${env:UET_FRAMEWORK_TARGET}/dotnet/dotnet-extracted -Value "done"
+ Write-Host "Setting up .NET SDK..."
+ New-Item -ItemType Directory ".dotnet" | Out-Null
+ Invoke-WebRequest https://dot.net/v1/dotnet-install.sh -UseBasicParsing -OutFile ".dotnet/dotnet-install.sh"
+ chmod a+x .dotnet/dotnet-install.sh
+ ./.dotnet/dotnet-install.sh --channel STS --install-dir ".dotnet/dotnet"
+ if ($LastExitCode -ne 0) {
+ exit $LastExitCode
+ }
+ Set-Content -Path .dotnet/dotnet/dotnet-extracted -Value "done"
}
- name: Add .NET SDK to PATH (Windows)
if: ${{ runner.os == 'Windows' }}
shell: pwsh
- env:
- UET_FRAMEWORK_TARGET: ${{ inputs.UET_FRAMEWORK_TARGET }}
run: |
- Add-Content -Path "${env:GITHUB_PATH}" -Value ".dotnet-${env:UET_FRAMEWORK_TARGET}\dotnet"
+ Add-Content -Path "${env:GITHUB_PATH}" -Value ".dotnet\dotnet"
- name: Add .NET SDK to PATH (macOS)
if: ${{ runner.os == 'macOS' }}
shell: pwsh
- env:
- UET_FRAMEWORK_TARGET: ${{ inputs.UET_FRAMEWORK_TARGET }}
run: |
- Add-Content -Path "${env:GITHUB_PATH}" -Value ".dotnet-${env:UET_FRAMEWORK_TARGET}/dotnet"
\ No newline at end of file
+ Add-Content -Path "${env:GITHUB_PATH}" -Value ".dotnet/dotnet"
+ - name: Add .NET SDK to PATH (Linux)
+ if: ${{ runner.os == 'Linux' }}
+ shell: pwsh
+ run: |
+ Add-Content -Path "${env:GITHUB_PATH}" -Value ".dotnet/dotnet"
\ No newline at end of file
diff --git a/.github/actions/upload-artifact/action.yml b/.github/actions/upload-artifact/action.yml
index 7c841144..8b603667 100644
--- a/.github/actions/upload-artifact/action.yml
+++ b/.github/actions/upload-artifact/action.yml
@@ -129,12 +129,27 @@ runs:
path: |
**/*.nupkg
- - name: Upload Test Results
- if: ${{ inputs.UET_ARTIFACT_NAME == 'test-results' }}
+ - name: Upload Windows Test Results
+ if: ${{ inputs.UET_ARTIFACT_NAME == 'test-results-win' }}
uses: actions/upload-artifact@v4
with:
- name: test-results
+ name: test-results-win
if-no-files-found: error
path: |
TestResults/*.test-result.trx
-
\ No newline at end of file
+ - name: Upload Linux Test Results
+ if: ${{ inputs.UET_ARTIFACT_NAME == 'test-results-linux' }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-results-linux
+ if-no-files-found: error
+ path: |
+ TestResults/*.test-result.trx
+ - name: Upload Cloud Framework Test Results
+ if: ${{ inputs.UET_ARTIFACT_NAME == 'test-results-cf' }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-results-cf
+ if-no-files-found: error
+ path: |
+ TestResults/*.test-result.trx
\ No newline at end of file
diff --git a/.github/workflows/test-publish.yml b/.github/workflows/test-publish.yml
index eb0c90e8..644027c5 100644
--- a/.github/workflows/test-publish.yml
+++ b/.github/workflows/test-publish.yml
@@ -19,7 +19,21 @@ jobs:
- name: Report Test Results
uses: dorny/test-reporter@v1
with:
- artifact: test-results
+ artifact: test-results-win
name: Windows Test Results
path: '*.test-result.trx'
+ reporter: dotnet-trx
+ - name: Report Test Results
+ uses: dorny/test-reporter@v1
+ with:
+ artifact: test-results-linux
+ name: Linux Test Results
+ path: '*.test-result.trx'
+ reporter: dotnet-trx
+ - name: Report Test Results
+ uses: dorny/test-reporter@v1
+ with:
+ artifact: test-results-cf
+ name: Cloud Framework Test Results
+ path: '*.test-result.trx'
reporter: dotnet-trx
\ No newline at end of file
diff --git a/.github/workflows/uet.yml b/.github/workflows/uet.yml
index 83c00c89..41e78b6b 100644
--- a/.github/workflows/uet.yml
+++ b/.github/workflows/uet.yml
@@ -412,7 +412,7 @@ jobs:
exit $LastExitCode
}
foreach ($Item in (Get-ChildItem UET -Filter *.Tests)) {
- if (Test-Path "$($Item.FullName)/$($Item.Name).csproj") {
+ if ((Test-Path "$($Item.FullName)/$($Item.Name).csproj") -and ($Item.Name -ne "Redpoint.CloudFramework.Tests")) {
Write-Host "============ STARTING: $($Item.Name) ============"
dotnet test --logger:"console" --logger:"trx;LogFileName=$($Item.Name).test-result.trx" --results-directory "$((Get-Location).Path)\TestResults" "$($Item.FullName)/bin/Release/${{ env.UET_FRAMEWORK_TARGET }}/$($Item.Name).dll"
if ($LastExitCode -ne 0) {
@@ -425,7 +425,7 @@ jobs:
- name: Upload Test Results
uses: ./.github/actions/upload-artifact
with:
- UET_ARTIFACT_NAME: test-results
+ UET_ARTIFACT_NAME: test-results-win
UET_FRAMEWORK_TARGET: ${{ env.UET_FRAMEWORK_TARGET }}
- name: Upload Packages
uses: ./.github/actions/upload-artifact
@@ -457,6 +457,47 @@ jobs:
echo "Package version: $UET_PACKAGE_VERSION"
dotnet build -c Release /p:PackageVersion=$UET_PACKAGE_VERSION UET/UET.sln
+ libs-linux:
+ name: "Build and Test Libraries on Linux"
+ runs-on: ubuntu-latest
+ needs:
+ - timestamp
+ env:
+ UET_PACKAGE_VERSION: ${{ needs.timestamp.outputs.version }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Install .NET SDK
+ uses: ./.github/actions/dotnet-sdk
+ with:
+ UET_FRAMEWORK_TARGET: ${{ env.UET_FRAMEWORK_TARGET }}
+ - name: Build and Test Libraries on Linux
+ shell: pwsh
+ run: |
+ dotnet build -c Release "/p:PackageVersion=${env:UET_PACKAGE_VERSION}" UET/UET.sln
+ if ($LastExitCode -ne 0) {
+ Write-Host "dotnet build (UET.sln) failed with exit code $LastExitCode"
+ exit $LastExitCode
+ }
+ foreach ($Item in (Get-ChildItem UET -Filter *.Tests)) {
+ if ((Test-Path "$($Item.FullName)/$($Item.Name).csproj") -and ($Item.Name -ne "Redpoint.CloudFramework.Tests")) {
+ Write-Host "============ STARTING: $($Item.Name) ============"
+ dotnet test --logger:"console" --logger:"trx;LogFileName=$($Item.Name).test-result.trx" --results-directory "$((Get-Location).Path)\TestResults" "$($Item.FullName)/bin/Release/${{ env.UET_FRAMEWORK_TARGET }}/$($Item.Name).dll"
+ if ($LastExitCode -ne 0) {
+ Write-Host "============ FAILED: $($Item.Name) ============"
+ exit $LastExitCode
+ }
+ Write-Host "============ PASSED: $($Item.Name) ============"
+ }
+ }
+ - name: Upload Test Results
+ uses: ./.github/actions/upload-artifact
+ with:
+ UET_ARTIFACT_NAME: test-results-linux
+ UET_FRAMEWORK_TARGET: ${{ env.UET_FRAMEWORK_TARGET }}
+
pass-2-win:
name: "Build Windows Pass 2"
runs-on: windows-latest
@@ -667,6 +708,7 @@ jobs:
- timestamp
- libs-win
- libs-mac
+ - libs-linux
- uefs-win
- uefs-mac
- shim-win
@@ -675,6 +717,7 @@ jobs:
- pass-2-win
- pass-2-mac
- pass-2-linux
+ - cf-linux-tests
env:
UET_PACKAGE_VERSION: ${{ needs.timestamp.outputs.version }}
steps:
@@ -715,6 +758,7 @@ jobs:
- timestamp
- libs-win
- libs-mac
+ - libs-linux
- uefs-win
- uefs-mac
- shim-win
@@ -723,6 +767,7 @@ jobs:
- pass-2-win
- pass-2-mac
- pass-2-linux
+ - cf-linux-tests
env:
UET_PACKAGE_VERSION: ${{ needs.timestamp.outputs.version }}
steps:
@@ -808,6 +853,7 @@ jobs:
- timestamp
- libs-win
- libs-mac
+ - libs-linux
- uefs-win
- uefs-mac
- shim-win
@@ -816,6 +862,7 @@ jobs:
- pass-2-win
- pass-2-mac
- pass-2-linux
+ - cf-linux-tests
env:
UET_PACKAGE_VERSION: ${{ needs.timestamp.outputs.version }}
steps:
@@ -853,6 +900,7 @@ jobs:
- timestamp
- libs-win
- libs-mac
+ - libs-linux
- uefs-win
- uefs-mac
- shim-win
@@ -861,6 +909,7 @@ jobs:
- pass-2-win
- pass-2-mac
- pass-2-linux
+ - cf-linux-tests
env:
UET_PACKAGE_VERSION: ${{ needs.timestamp.outputs.version }}
steps:
@@ -907,6 +956,7 @@ jobs:
- timestamp
- libs-win
- libs-mac
+ - libs-linux
- uefs-win
- uefs-mac
- shim-win
@@ -915,6 +965,7 @@ jobs:
- pass-2-win
- pass-2-mac
- pass-2-linux
+ - cf-linux-tests
env:
UET_PACKAGE_VERSION: ${{ needs.timestamp.outputs.version }}
steps:
@@ -948,3 +999,56 @@ jobs:
docker buildx create --name img-builder-linux --use --platform linux/amd64
docker buildx build --platform linux/amd64 --output=type=registry -f UET/Lib/Container/linux-wine.Dockerfile -t "ghcr.io/redpointgames/uet/uet:${env:UET_PACKAGE_VERSION}-wine" --build-arg UET_TARGET_FRAMEWORK=${{ env.UET_FRAMEWORK_TARGET }} .
docker buildx build --platform linux/amd64 --output=type=registry -f UET/Lib/Container/linux-wine.Dockerfile -t "ghcr.io/redpointgames/uet/uet:latest-wine" --build-arg UET_TARGET_FRAMEWORK=${{ env.UET_FRAMEWORK_TARGET }} .
+
+ cf-linux-tests:
+ name: "Test Cloud Framework"
+ runs-on: ubuntu-latest
+ container:
+ image: mcr.microsoft.com/dotnet/sdk:latest
+ services:
+ redis:
+ image: redis:6.0.10
+ ports:
+ - 6379:6379
+ pubsub:
+ image: ghcr.io/redpointgames/uet/pubsub-emulator:latest
+ ports:
+ - 9000:9000
+ datastore:
+ image: ghcr.io/redpointgames/uet/datastore-emulator:latest
+ ports:
+ - 9001:9001
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Install .NET SDK
+ uses: ./.github/actions/dotnet-sdk
+ with:
+ UET_FRAMEWORK_TARGET: ${{ env.UET_FRAMEWORK_TARGET }}
+ - name: Build and Test Cloud Framework
+ shell: pwsh
+ run: |
+ foreach ($Item in (Get-ChildItem UET -Filter Redpoint.CloudFramework.Tests)) {
+ if (Test-Path "$($Item.FullName)/$($Item.Name).csproj") {
+ dotnet build -c Release "$($Item.FullName)/$($Item.Name).csproj"
+ if ($LastExitCode -ne 0) {
+ Write-Host "dotnet build ($($Item.FullName)/$($Item.Name).csproj) failed with exit code $LastExitCode"
+ exit $LastExitCode
+ }
+
+ Write-Host "============ STARTING: $($Item.Name) ============"
+ dotnet test --logger:"console" --logger:"trx;LogFileName=$($Item.Name).test-result.trx" --results-directory "$((Get-Location).Path)\TestResults" "$($Item.FullName)/bin/Release/${{ env.UET_FRAMEWORK_TARGET }}/$($Item.Name).dll"
+ if ($LastExitCode -ne 0) {
+ Write-Host "============ FAILED: $($Item.Name) ============"
+ exit $LastExitCode
+ }
+ Write-Host "============ PASSED: $($Item.Name) ============"
+ }
+ }
+ - name: Upload Test Results
+ uses: ./.github/actions/upload-artifact
+ with:
+ UET_ARTIFACT_NAME: test-results-cf
+ UET_FRAMEWORK_TARGET: ${{ env.UET_FRAMEWORK_TARGET }}
diff --git a/UET/Lib/Framework.AspNetCore.Build.props b/UET/Lib/Framework.AspNetCore.Build.props
new file mode 100644
index 00000000..9c3637f2
--- /dev/null
+++ b/UET/Lib/Framework.AspNetCore.Build.props
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/AspNetFileSystem.cs b/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/AspNetFileSystem.cs
new file mode 100644
index 00000000..4b6b2ad2
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/AspNetFileSystem.cs
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+using Microsoft.AspNetCore.Hosting;
+using React.Exceptions;
+
+namespace React.AspNet
+{
+ ///
+ /// Handles file system functionality, such as reading files. Maps all paths from
+ /// application-relative (~/...) to full paths using ASP.NET's MapPath method.
+ ///
+ public class AspNetFileSystem : FileSystemBase
+ {
+ private readonly IWebHostEnvironment _hostingEnv;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The .NET Core hosting environment
+ public AspNetFileSystem(IWebHostEnvironment hostingEnv)
+ {
+ _hostingEnv = hostingEnv;
+ }
+
+ ///
+ /// Converts a path from an application relative path (~/...) to a full filesystem path
+ ///
+ /// App-relative path of the file
+ /// Full path of the file
+ public override string MapPath(string relativePath)
+ {
+ if (_hostingEnv.WebRootPath == null)
+ {
+ throw new ReactException("WebRootPath was null, has the wwwroot folder been deployed along with your app?");
+ }
+
+ if (relativePath.StartsWith(_hostingEnv.WebRootPath))
+ {
+ return relativePath;
+ }
+ relativePath = relativePath.TrimStart('~').TrimStart('/').TrimStart('\\');
+
+ return Path.GetFullPath(Path.Combine(_hostingEnv.WebRootPath, relativePath));
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/MemoryFileCacheCore.cs b/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/MemoryFileCacheCore.cs
new file mode 100644
index 00000000..fa663bc7
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/MemoryFileCacheCore.cs
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.AspNetCore.Hosting;
+
+namespace React.AspNet
+{
+ ///
+ /// Memory cache implementation for React.ICache. Uses IMemoryCache from .NET Core.
+ ///
+ public class MemoryFileCacheCore : ICache
+ {
+ private readonly IMemoryCache _cache;
+ private readonly IWebHostEnvironment _hostingEnv;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The cache to use
+ /// The ASP.NET hosting environment.
+ public MemoryFileCacheCore(IMemoryCache cache, IWebHostEnvironment hostingEnv)
+ {
+ _cache = cache;
+ _hostingEnv = hostingEnv;
+ }
+
+ ///
+ /// Get an item from the cache. Returns if the item does
+ /// not exist.
+ ///
+ /// Type of data
+ /// The cache key
+ /// Value to return if item is not in the cache
+ /// Data from cache, otherwise
+ public T Get(string key, T fallback = default(T))
+ {
+ return (T)(_cache.Get(key) ?? fallback);
+ }
+
+ ///
+ /// Sets an item in the cache.
+ ///
+ /// Type of data
+ /// The cache key
+ /// Data to cache
+ ///
+ /// Sliding expiration, if cache key is not accessed in this time period it will
+ /// automatically be removed from the cache
+ ///
+ ///
+ /// Filenames this cached item is dependent on. If any of these files change, the cache
+ /// will be cleared automatically
+ ///
+ public void Set(string key, T data, TimeSpan slidingExpiration, IEnumerable cacheDependencyFiles = null)
+ {
+ if (data == null)
+ {
+ _cache.Remove(key);
+ return;
+ }
+
+ var options = new MemoryCacheEntryOptions
+ {
+ SlidingExpiration = slidingExpiration,
+ };
+
+ if (cacheDependencyFiles != null)
+ {
+ foreach (var file in cacheDependencyFiles)
+ {
+ var relativePath = file.Replace(_hostingEnv.WebRootPath, string.Empty).TrimStart('\\', '/');
+ options.AddExpirationToken(_hostingEnv.WebRootFileProvider.Watch(relativePath));
+ }
+ }
+
+ _cache.Set(key, data, options);
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/README.md b/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/README.md
new file mode 100644
index 00000000..b6d307a7
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/README.md
@@ -0,0 +1,5 @@
+# Redpoint.ThirdParty.React.AspNet
+
+This is a fork of React.NET (https://github.com/reactjs/react.net) that adds support for React v18.
+
+If you want to use this fork in an ASP.NET Core project, use the `Redpoint.ThirdParty.React.AspNet` package.
\ No newline at end of file
diff --git a/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/ReactBuilderExtensions.cs b/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/ReactBuilderExtensions.cs
new file mode 100644
index 00000000..b91bc3a0
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/ReactBuilderExtensions.cs
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace React.AspNet
+{
+ ///
+ /// Handles registering ReactJS.NET middleware in an ASP.NET .
+ ///
+ public static class ReactBuilderExtensions
+ {
+ ///
+ /// Initialises ReactJS.NET for this application
+ ///
+ /// ASP.NET application builder
+ /// ReactJS.NET configuration
+ /// The application builder (for chaining)
+ public static IApplicationBuilder UseReact(
+ this IApplicationBuilder app,
+ Action configure
+ )
+ {
+ // Apply configuration.
+ configure(app.ApplicationServices.GetRequiredService());
+
+ return app;
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/ReactServiceCollectionExtensions.cs b/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/ReactServiceCollectionExtensions.cs
new file mode 100644
index 00000000..148883aa
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/ReactServiceCollectionExtensions.cs
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+using JavaScriptEngineSwitcher.Core;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace React.AspNet
+{
+ ///
+ /// Handles registering ReactJS.NET services in the ASP.NET .
+ ///
+ public static class ReactServiceCollectionExtensions
+ {
+ ///
+ /// Registers all services required for ReactJS.NET
+ ///
+ /// ASP.NET services
+ /// The service collection (for chaining)
+ public static IServiceCollection AddReact(this IServiceCollection services)
+ {
+ services.AddSingleton();
+ services.AddScoped();
+ services.AddSingleton(sp => JsEngineSwitcher.Current);
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddScoped();
+
+ services.AddSingleton();
+ services.AddSingleton();
+
+ return services;
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/Redpoint.ThirdParty.React.AspNet.Middleware.csproj b/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/Redpoint.ThirdParty.React.AspNet.Middleware.csproj
new file mode 100644
index 00000000..be4904c3
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.AspNet.Middleware/Redpoint.ThirdParty.React.AspNet.Middleware.csproj
@@ -0,0 +1,30 @@
+
+
+
+
+
+ React.AspNet.Middleware
+ React.AspNet.Middleware
+
+
+
+
+
+
+ A fork of React.NET (https://github.com/reactjs/react.net) that adds support for React v18.
+ Redpoint.ThirdParty.React.AspNet.Middleware
+ react, react.net
+ MIT
+ June Rhodes, Daniel Lo Nigro
+
+
+
+
+
+
+
+
+ 7035
+
+
+
diff --git a/UET/Lib/Redpoint.ThirdParty.React.AspNet/HtmlHelperExtensions.cs b/UET/Lib/Redpoint.ThirdParty.React.AspNet/HtmlHelperExtensions.cs
new file mode 100644
index 00000000..85faed07
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.AspNet/HtmlHelperExtensions.cs
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+using System.Text;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Mvc.Rendering;
+using Microsoft.AspNetCore.Html;
+using IHtmlString = Microsoft.AspNetCore.Html.IHtmlContent;
+using Microsoft.AspNetCore.Mvc;
+
+namespace React.AspNet
+{
+ ///
+ /// HTML Helpers for utilising React from an ASP.NET MVC application.
+ ///
+ public static class HtmlHelperExtensions
+ {
+ [ThreadStatic]
+ private static StringWriter _sharedStringWriter;
+
+ ///
+ /// Renders the specified React component
+ ///
+ /// Type of the props
+ /// HTML helper
+ /// Name of the component
+ /// Props to initialise the component with
+ /// HTML tag to wrap the component in. Defaults to <div>
+ /// ID to use for the container HTML tag. Defaults to an auto-generated ID
+ /// Skip rendering server-side and only output client-side initialisation code. Defaults to false
+ /// Skip rendering React specific data-attributes, container and client-side initialisation during server side rendering. Defaults to false
+ /// HTML class(es) to set on the container tag
+ /// A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)
+ /// Functions to call during component render
+ /// The component's HTML
+ public static IHtmlString React(
+ this IHtmlHelper htmlHelper,
+ string componentName,
+ T props,
+ string htmlTag = null,
+ string containerId = null,
+ bool clientOnly = false,
+ bool serverOnly = false,
+ string containerClass = null,
+ Action exceptionHandler = null,
+ IRenderFunctions renderFunctions = null
+ )
+ {
+ var instance = htmlHelper.ViewContext.HttpContext.RequestServices.GetRequiredService();
+
+ try
+ {
+ var reactComponent = instance.CreateComponent(componentName, props, containerId, clientOnly, serverOnly);
+ if (!string.IsNullOrEmpty(htmlTag))
+ {
+ reactComponent.ContainerTag = htmlTag;
+ }
+
+ if (!string.IsNullOrEmpty(containerClass))
+ {
+ reactComponent.ContainerClass = containerClass;
+ }
+
+ return RenderToString(writer => reactComponent.RenderHtml(writer, clientOnly, serverOnly, exceptionHandler, renderFunctions));
+ }
+ finally
+ {
+ instance.ReturnEngineToPool();
+ }
+ }
+
+ ///
+ /// Renders the specified React component, along with its client-side initialisation code.
+ /// Normally you would use the method, but
+ /// is useful when rendering self-contained partial views.
+ ///
+ /// Type of the props
+ /// HTML helper
+ /// Name of the component
+ /// Props to initialise the component with
+ /// HTML tag to wrap the component in. Defaults to <div>
+ /// ID to use for the container HTML tag. Defaults to an auto-generated ID
+ /// Skip rendering server-side and only output client-side initialisation code. Defaults to false
+ /// Skip rendering React specific data-attributes, container and client-side initialisation during server side rendering. Defaults to false
+ /// HTML class(es) to set on the container tag
+ /// A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)
+ /// Functions to call during component render
+ /// The component's HTML
+ public static IHtmlString ReactWithInit(
+ this IHtmlHelper htmlHelper,
+ string componentName,
+ T props,
+ string htmlTag = null,
+ string containerId = null,
+ bool clientOnly = false,
+ bool serverOnly = false,
+ string containerClass = null,
+ Action exceptionHandler = null,
+ IRenderFunctions renderFunctions = null
+ )
+ {
+ var instance = htmlHelper.ViewContext.HttpContext.RequestServices.GetRequiredService();
+
+ try
+ {
+ var reactComponent = instance.CreateComponent(componentName, props, containerId, clientOnly);
+ if (!string.IsNullOrEmpty(htmlTag))
+ {
+ reactComponent.ContainerTag = htmlTag;
+ }
+
+ if (!string.IsNullOrEmpty(containerClass))
+ {
+ reactComponent.ContainerClass = containerClass;
+ }
+
+ return RenderToString(writer =>
+ {
+ reactComponent.RenderHtml(writer, clientOnly, serverOnly, exceptionHandler: exceptionHandler, renderFunctions);
+ writer.WriteLine();
+ WriteScriptTag(instance, writer, bodyWriter => reactComponent.RenderJavaScript(bodyWriter, waitForDOMContentLoad: true));
+ });
+ }
+ finally
+ {
+ instance.ReturnEngineToPool();
+ }
+ }
+
+ ///
+ /// Renders the JavaScript required to initialise all components client-side. This will
+ /// attach event handlers to the server-rendered HTML.
+ ///
+ /// JavaScript for all components
+ public static IHtmlString ReactInitJavaScript(this IHtmlHelper htmlHelper, bool clientOnly = false)
+ {
+ var instance = htmlHelper.ViewContext.HttpContext.RequestServices.GetRequiredService();
+
+ try
+ {
+ return RenderToString(writer =>
+ {
+ WriteScriptTag(instance, writer, bodyWriter => instance.GetInitJavaScript(bodyWriter, clientOnly));
+ });
+ }
+ finally
+ {
+ instance.ReturnEngineToPool();
+ }
+ }
+
+ ///
+ /// Returns script tags based on the webpack asset manifest
+ ///
+ ///
+ /// Optional IUrlHelper instance. Enables the use of tilde/relative (~/) paths inside the expose-components.js file.
+ ///
+ public static IHtmlString ReactGetScriptPaths(this IHtmlHelper htmlHelper, IUrlHelper urlHelper = null)
+ {
+ var instance = htmlHelper.ViewContext.HttpContext.RequestServices.GetRequiredService();
+
+ string nonce = instance.Configuration.ScriptNonceProvider != null
+ ? $" nonce=\"{instance.Configuration.ScriptNonceProvider()}\""
+ : "";
+
+ return new HtmlString(string.Join("", instance.GetScriptPaths()
+ .Select(scriptPath => $"")));
+ }
+
+ ///
+ /// Returns style tags based on the webpack asset manifest
+ ///
+ ///
+ /// Optional IUrlHelper instance. Enables the use of tilde/relative (~/) paths inside the expose-components.js file.
+ ///
+ public static IHtmlString ReactGetStylePaths(this IHtmlHelper htmlHelper, IUrlHelper urlHelper = null)
+ {
+ var instance = htmlHelper.ViewContext.HttpContext.RequestServices.GetRequiredService();
+
+ return new HtmlString(string.Join("", instance.GetStylePaths()
+ .Select(stylePath => $"")));
+ }
+
+ private static IHtmlString RenderToString(Action withWriter)
+ {
+ var stringWriter = _sharedStringWriter;
+ if (stringWriter != null)
+ {
+ stringWriter.GetStringBuilder().Clear();
+ }
+ else
+ {
+ _sharedStringWriter = stringWriter = new StringWriter(new StringBuilder(128));
+ }
+
+ withWriter(stringWriter);
+ return new HtmlString(stringWriter.ToString());
+ }
+
+ private static void WriteScriptTag(IReactEnvironment instance, TextWriter writer, Action bodyWriter)
+ {
+ writer.Write("");
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.AspNet/README.md b/UET/Lib/Redpoint.ThirdParty.React.AspNet/README.md
new file mode 100644
index 00000000..d465a966
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.AspNet/README.md
@@ -0,0 +1,5 @@
+# Redpoint.ThirdParty.React.AspNet
+
+This is a fork of React.NET (https://github.com/reactjs/react.net) that adds support for React v18.
+
+If you want to use this fork in an ASP.NET Core project, this package is the one to use.
\ No newline at end of file
diff --git a/UET/Lib/Redpoint.ThirdParty.React.AspNet/Redpoint.ThirdParty.React.AspNet.csproj b/UET/Lib/Redpoint.ThirdParty.React.AspNet/Redpoint.ThirdParty.React.AspNet.csproj
new file mode 100644
index 00000000..ab296654
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.AspNet/Redpoint.ThirdParty.React.AspNet.csproj
@@ -0,0 +1,31 @@
+
+
+
+
+
+ React.AspNet
+ React.AspNet
+
+
+
+
+
+
+
+
+
+
+
+ A fork of React.NET (https://github.com/reactjs/react.net) that adds support for React v18.
+ Redpoint.ThirdParty.React.AspNet
+ react, react.net
+ MIT
+ June Rhodes, Daniel Lo Nigro
+
+
+
+
+ 7035
+
+
+
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactConfigurationException.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactConfigurationException.cs
new file mode 100644
index 00000000..c7a48f99
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactConfigurationException.cs
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React.Exceptions
+{
+ ///
+ /// Thrown when an error occurs while reading a site configuration file.
+ ///
+ public class ReactConfigurationException : ReactException
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the error.
+ public ReactConfigurationException(string message) : base(message) { }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.
+ public ReactConfigurationException(string message, Exception innerException)
+ : base(message, innerException) { }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactEngineNotFoundException.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactEngineNotFoundException.cs
new file mode 100644
index 00000000..4d95352b
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactEngineNotFoundException.cs
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React.Exceptions
+{
+ ///
+ /// Thrown when no valid JavaScript engine is found.
+ ///
+ public class ReactEngineNotFoundException : ReactException
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the error.
+ public ReactEngineNotFoundException(string message) : base(message) { }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactException.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactException.cs
new file mode 100644
index 00000000..fada5e62
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactException.cs
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React.Exceptions
+{
+ ///
+ /// Base class for all ReactJS.NET exceptions
+ ///
+ public class ReactException : Exception
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ReactException() : base() { }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the error.
+ public ReactException(string message) : base(message) { }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.
+ public ReactException(string message, Exception innerException)
+ : base(message, innerException) { }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactInvalidComponentException.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactInvalidComponentException.cs
new file mode 100644
index 00000000..62263dcd
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactInvalidComponentException.cs
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React.Exceptions
+{
+ ///
+ /// Thrown when a non-existent component is rendered.
+ ///
+ public class ReactInvalidComponentException : ReactException
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the error.
+ public ReactInvalidComponentException(string message) : base(message) { }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.
+ public ReactInvalidComponentException(string message, Exception innerException)
+ : base(message, innerException) { }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactNotInitialisedException.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactNotInitialisedException.cs
new file mode 100644
index 00000000..7d836828
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactNotInitialisedException.cs
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React.Exceptions
+{
+ ///
+ /// Thrown when React has not been initialised correctly.
+ ///
+ public class ReactNotInitialisedException : ReactException
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the error.
+ public ReactNotInitialisedException(string message) : base(message) { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.
+ public ReactNotInitialisedException(string message, Exception innerException)
+ : base(message, innerException) { }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactScriptLoadException.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactScriptLoadException.cs
new file mode 100644
index 00000000..1c6b3eb4
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactScriptLoadException.cs
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React.Exceptions
+{
+ ///
+ /// Thrown when an error is encountered while loading a JavaScript file.
+ ///
+ public class ReactScriptLoadException : ReactException
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the error.
+ public ReactScriptLoadException(string message) : base(message) { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.
+ public ReactScriptLoadException(string message, Exception innerException)
+ : base(message, innerException) { }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactScriptPrecompilationNotAvailableException.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactScriptPrecompilationNotAvailableException.cs
new file mode 100644
index 00000000..b93702fc
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactScriptPrecompilationNotAvailableException.cs
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React.Exceptions
+{
+ ///
+ /// Thrown when the script pre-compilation is not available.
+ ///
+ public class ReactScriptPrecompilationNotAvailableException : ReactException
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the error.
+ public ReactScriptPrecompilationNotAvailableException(string message) : base(message) { }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactServerRenderingException.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactServerRenderingException.cs
new file mode 100644
index 00000000..024712c8
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/Exceptions/ReactServerRenderingException.cs
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React.Exceptions
+{
+ ///
+ /// Thrown when an error occurs during server rendering of a React component.
+ ///
+ public class ReactServerRenderingException : ReactException
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The message that describes the error.
+ public ReactServerRenderingException(string message) : base(message) { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The error message that explains the reason for the exception.
+ /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified.
+ public ReactServerRenderingException(string message, Exception innerException)
+ : base(message, innerException) { }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/FileCacheHash.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/FileCacheHash.cs
new file mode 100644
index 00000000..d449b6cc
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/FileCacheHash.cs
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+using System.Security.Cryptography;
+using System.Text;
+
+namespace React
+{
+ ///
+ /// Handles calculating a hash value for validating a file-based cache.
+ ///
+ public class FileCacheHash : IFileCacheHash
+ {
+ ///
+ /// Prefix used for hash line in transformed file. Used for caching.
+ ///
+ private const string HASH_PREFIX = "// @hash v3-";
+
+ // TODO: Do we really need to use SHA1Cng specifically?
+ ///
+ /// Algorithm for calculating file hashes
+ ///
+ private readonly HashAlgorithm _hash = SHA1.Create();
+
+ ///
+ /// Calculates a hash for the specified input
+ ///
+ /// Input string
+ /// Hash of the input
+ public string CalculateHash(string input)
+ {
+ var inputBytes = Encoding.UTF8.GetBytes(input);
+ var hash = _hash.ComputeHash(inputBytes);
+ return BitConverter.ToString(hash).Replace("-", string.Empty);
+ }
+
+ ///
+ /// Validates that the cache's hash is valid. This is used to ensure the input has not
+ /// changed, and to invalidate the cache if so.
+ ///
+ /// Contents retrieved from cache
+ /// Hash of the input
+ /// true if the cache is still valid
+ public virtual bool ValidateHash(string cacheContents, string hash)
+ {
+ if (string.IsNullOrWhiteSpace(cacheContents))
+ {
+ return false;
+ }
+
+ // Check if first line is hash
+ var firstLineBreak = cacheContents.IndexOfAny(new[] { '\r', '\n' });
+ if (firstLineBreak == -1)
+ {
+ return false;
+ }
+ var firstLine = cacheContents.Substring(0, firstLineBreak);
+ if (!firstLine.StartsWith(HASH_PREFIX))
+ {
+ // Cache doesn't have hash - Err on the side of caution and invalidate it.
+ return false;
+ }
+ var cacheHash = firstLine.Replace(HASH_PREFIX, string.Empty);
+ return cacheHash == hash;
+ }
+
+ ///
+ /// Prepends the hash prefix to the hash
+ ///
+ /// Hash to prepend prefix to
+ /// Hash with prefix
+ public virtual string AddPrefix(string hash)
+ {
+ return HASH_PREFIX + hash;
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/FileSystemBase.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/FileSystemBase.cs
new file mode 100644
index 00000000..f6598761
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/FileSystemBase.cs
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+using System.Text;
+
+namespace React
+{
+ ///
+ /// Handles file system functionality, such as reading files.
+ ///
+ abstract public class FileSystemBase : IFileSystem
+ {
+ ///
+ /// Prefix for relative paths
+ ///
+ public const string RELATIVE_PREFIX = "~/";
+
+ ///
+ /// Converts a path from an application relative path (~/...) to a full filesystem path
+ ///
+ /// App-relative path of the file
+ /// Full path of the file
+ public abstract string MapPath(string relativePath);
+
+ ///
+ /// Converts a path from a full filesystem path to an application relative path (~/...)
+ ///
+ /// Full path of the file
+ /// App-relative path of the file
+ public virtual string ToRelativePath(string absolutePath)
+ {
+ var root = MapPath(RELATIVE_PREFIX);
+ return absolutePath.Replace(root, RELATIVE_PREFIX).Replace('\\', '/');
+ }
+
+ ///
+ /// Reads the contents of a file as a string.
+ ///
+ /// App-relative path of the file
+ /// Contents of the file
+ public virtual string ReadAsString(string relativePath)
+ {
+ return File.ReadAllText(MapPath(relativePath), Encoding.UTF8);
+ }
+
+ ///
+ /// Writes a string to a file
+ ///
+ /// App-relative path of the file
+ /// Contents of the file
+ public virtual void WriteAsString(string relativePath, string contents)
+ {
+ File.WriteAllText(MapPath(relativePath), contents, Encoding.UTF8);
+ }
+
+ ///
+ /// Determines if the specified file exists
+ ///
+ /// App-relative path of the file
+ /// true if the file exists
+ public virtual bool FileExists(string relativePath)
+ {
+ return File.Exists(MapPath(relativePath));
+ }
+
+ ///
+ /// Gets all the file paths that match the specified pattern
+ ///
+ /// Pattern to search for (eg. "~/Scripts/*.js")
+ /// File paths that match the pattern
+ public virtual IEnumerable Glob(string glob)
+ {
+ var path = MapPath(Path.GetDirectoryName(glob));
+ var searchPattern = Path.GetFileName(glob);
+ return Directory.EnumerateFiles(path, searchPattern).Select(ToRelativePath);
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/FileSystemExtensions.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/FileSystemExtensions.cs
new file mode 100644
index 00000000..9a5eee0a
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/FileSystemExtensions.cs
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React
+{
+ ///
+ /// Extension methods relating to file system paths.
+ ///
+ public static class FileSystemExtensions
+ {
+ ///
+ /// Determines if the specified string is a glob pattern that can be used with
+ /// .
+ ///
+ /// String
+ /// true if the specified string is a glob pattern
+ public static bool IsGlobPattern(this string input)
+ {
+ return input.Contains("*") || input.Contains("?");
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/ICache.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/ICache.cs
new file mode 100644
index 00000000..6fc01871
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/ICache.cs
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React
+{
+ ///
+ /// Handles caching of data and optionally tracking dependencies
+ ///
+ public interface ICache
+ {
+ ///
+ /// Get an item from the cache. Returns if the item does
+ /// not exist.
+ ///
+ /// Type of data
+ /// The cache key
+ /// Value to return if item is not in the cache
+ /// Data from cache, otherwise
+ T Get(string key, T fallback = default(T));
+
+ ///
+ /// Sets an item in the cache.
+ ///
+ /// Type of data
+ /// The cache key
+ /// Data to cache
+ ///
+ /// Sliding expiration, if cache key is not accessed in this time period it will
+ /// automatically be removed from the cache
+ ///
+ ///
+ /// Filenames this cached item is dependent on. If any of these files change, the cache
+ /// will be cleared automatically
+ ///
+ void Set(
+ string key,
+ T data,
+ TimeSpan slidingExpiration,
+ IEnumerable cacheDependencyFiles = null
+ );
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/IFileCacheHash.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/IFileCacheHash.cs
new file mode 100644
index 00000000..cfabfbd2
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/IFileCacheHash.cs
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React
+{
+ ///
+ /// Handles calculating a hash value for validating a file-based cache.
+ ///
+ public interface IFileCacheHash
+ {
+ ///
+ /// Calculates a hash for the specified input
+ ///
+ /// Input string
+ /// Hash of the input
+ string CalculateHash(string input);
+
+ ///
+ /// Validates that the cache's hash is valid. This is used to ensure the input has not
+ /// changed, and to invalidate the cache if so.
+ ///
+ /// Contents retrieved from cache
+ /// Hash of the input
+ /// true if the cache is still valid
+ bool ValidateHash(string cacheContents, string hash);
+
+ ///
+ /// Prepends the hash prefix to the hash
+ ///
+ /// Hash to prepend prefix to
+ /// Hash with prefix
+ string AddPrefix(string hash);
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/IFileSystem.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/IFileSystem.cs
new file mode 100644
index 00000000..3fdf8c0f
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/IFileSystem.cs
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React
+{
+ ///
+ /// Handles file system functionality, such as reading files.
+ ///
+ public interface IFileSystem
+ {
+ ///
+ /// Converts a path from an application relative path (~/...) to a full filesystem path
+ ///
+ /// App-relative path of the file
+ /// Full path of the file
+ string MapPath(string relativePath);
+
+ ///
+ /// Converts a path from a full filesystem path to anan application relative path (~/...)
+ ///
+ /// Full path of the file
+ /// App-relative path of the file
+ string ToRelativePath(string absolutePath);
+
+ ///
+ /// Reads the contents of a file as a string.
+ ///
+ /// App-relative path of the file
+ /// Contents of the file
+ string ReadAsString(string relativePath);
+
+ ///
+ /// Writes a string to a file
+ ///
+ /// App-relative path of the file
+ /// Contents of the file
+ void WriteAsString(string relativePath, string contents);
+
+ ///
+ /// Determines if the specified file exists
+ ///
+ /// App-relative path of the file
+ /// true if the file exists
+ bool FileExists(string relativePath);
+
+ ///
+ /// Gets all the files that match the specified pattern
+ ///
+ /// Pattern to search for (eg. "~/Scripts/*.js")
+ /// File names that match the pattern
+ IEnumerable Glob(string glob);
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/IJavaScriptEngineFactory.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/IJavaScriptEngineFactory.cs
new file mode 100644
index 00000000..adb731d3
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/IJavaScriptEngineFactory.cs
@@ -0,0 +1,30 @@
+using JavaScriptEngineSwitcher.Core;
+using JSPool;
+
+namespace React
+{
+ ///
+ /// Handles creation of JavaScript engines. All methods are thread-safe.
+ ///
+ public interface IJavaScriptEngineFactory
+ {
+ ///
+ /// Gets the JavaScript engine for the current thread. It is recommended to use
+ /// instead, which will pool/reuse engines.
+ ///
+ /// The JavaScript engine
+ IJsEngine GetEngineForCurrentThread();
+
+ ///
+ /// Disposes the JavaScript engine for the current thread. This should only be used
+ /// if the engine was acquired through .
+ ///
+ void DisposeEngineForCurrentThread();
+
+ ///
+ /// Gets a JavaScript engine from the pool.
+ ///
+ /// The JavaScript engine
+ PooledJsEngine GetEngine();
+ }
+}
\ No newline at end of file
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/IReactComponent.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/IReactComponent.cs
new file mode 100644
index 00000000..f3e19c14
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/IReactComponent.cs
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React
+{
+ ///
+ /// Represents a React JavaScript component.
+ ///
+ public interface IReactComponent
+ {
+ ///
+ /// Gets or sets the props for this component
+ ///
+ object Props { get; set; }
+
+ ///
+ /// Gets or sets the name of the component
+ ///
+ string ComponentName { get; set; }
+
+ ///
+ /// Gets or sets the unique ID for the container of this component
+ ///
+ string ContainerId { get; set; }
+
+ ///
+ /// Gets or sets the HTML tag the component is wrapped in
+ ///
+ string ContainerTag { get; set; }
+
+ ///
+ /// Gets or sets the HTML class for the container of this component
+ ///
+ string ContainerClass { get; set; }
+
+ ///
+ /// Get or sets if this components only should be rendered server side
+ ///
+ bool ServerOnly { get; set; }
+
+ ///
+ /// Renders the HTML for this component. This will execute the component server-side and
+ /// return the rendered HTML.
+ ///
+ /// Only renders component container. Used for client-side only rendering.
+ /// Only renders the common HTML mark up and not any React specific data attributes. Used for server-side only rendering.
+ /// A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)
+ /// Functions to call during component render
+ /// HTML
+ string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false, Action exceptionHandler = null, IRenderFunctions renderFunctions = null);
+
+ ///
+ /// Renders the HTML for this component. This will execute the component server-side and
+ /// return the rendered HTML.
+ ///
+ /// The to which the content is written
+ /// Only renders component container. Used for client-side only rendering.
+ /// Only renders the common HTML mark up and not any React specific data attributes. Used for server-side only rendering.
+ /// A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)
+ /// Functions to call during component render
+ /// HTML
+ void RenderHtml(TextWriter writer, bool renderContainerOnly = false, bool renderServerOnly = false, Action exceptionHandler = null, IRenderFunctions renderFunctions = null);
+
+ ///
+ /// Renders the JavaScript required to initialise this component client-side. This will
+ /// initialise the React component, which includes attach event handlers to the
+ /// server-rendered HTML.
+ ///
+ /// JavaScript
+ string RenderJavaScript(bool waitForDOMContentLoad);
+
+ ///
+ /// Renders the JavaScript required to initialise this component client-side. This will
+ /// initialise the React component, which includes attach event handlers to the
+ /// server-rendered HTML.
+ ///
+ /// JavaScript
+ void RenderJavaScript(TextWriter writer, bool waitForDOMContentLoad);
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/IReactEnvironment.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/IReactEnvironment.cs
new file mode 100644
index 00000000..9c79db6e
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/IReactEnvironment.cs
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React
+{
+ ///
+ /// Request-specific ReactJS.NET environment. This is unique to the individual request and is
+ /// not shared.
+ ///
+ public interface IReactEnvironment
+ {
+ ///
+ /// Gets the version number of ReactJS.NET
+ ///
+ string Version { get; }
+
+ ///
+ /// Gets the name and version of the JavaScript engine in use by ReactJS.NET
+ ///
+ string EngineVersion { get; }
+
+ ///
+ /// Executes the provided JavaScript code.
+ ///
+ /// JavaScript to execute
+ void Execute(string code);
+
+ ///
+ /// Executes the provided JavaScript code, returning a result of the specified type.
+ ///
+ /// Type to return
+ /// Code to execute
+ /// Result of the JavaScript code
+ T Execute(string code);
+
+ ///
+ /// Executes the provided JavaScript function, returning a result of the specified type.
+ ///
+ /// Type to return
+ /// JavaScript function to execute
+ /// Arguments to pass to function
+ /// Result of the JavaScript code
+ T Execute(string function, params object[] args);
+
+ ///
+ /// Determines if the specified variable exists in the JavaScript engine
+ ///
+ /// Name of the variable
+ /// true if the variable exists; false otherwise
+ bool HasVariable(string name);
+
+ ///
+ /// Creates an instance of the specified React JavaScript component.
+ ///
+ /// Type of the props
+ /// Name of the component
+ /// Props to use
+ /// ID to use for the container HTML tag. Defaults to an auto-generated ID
+ /// True if server-side rendering will be bypassed. Defaults to false.
+ /// True if this component only should be rendered server-side. Defaults to false.
+ /// Skip adding to components list, which is used during GetInitJavascript
+ /// The component
+ IReactComponent CreateComponent(string componentName, T props, string containerId = null, bool clientOnly = false, bool serverOnly = false, bool skipLazyInit = false);
+
+ ///
+ /// Adds the provided to the list of components to render client side.
+ ///
+ /// Component to add to client side render list
+ /// True if server-side rendering will be bypassed. Defaults to false.
+ /// The component
+ IReactComponent CreateComponent(IReactComponent component, bool clientOnly = false);
+
+ ///
+ /// Renders the JavaScript required to initialise all components client-side. This will
+ /// attach event handlers to the server-rendered HTML.
+ ///
+ /// True if server-side rendering will be bypassed. Defaults to false.
+ /// JavaScript for all components
+ string GetInitJavaScript(bool clientOnly = false);
+
+ ///
+ /// Returns the currently held JS engine to the pool. (no-op if engine pooling is disabled)
+ ///
+ void ReturnEngineToPool();
+
+ ///
+ /// Gets the site-wide configuration.
+ ///
+ IReactSiteConfiguration Configuration { get; }
+
+ ///
+ /// Renders the JavaScript required to initialise all components client-side. This will
+ /// attach event handlers to the server-rendered HTML.
+ ///
+ /// The to which the content is written
+ /// True if server-side rendering will be bypassed. Defaults to false.
+ /// JavaScript for all components
+ void GetInitJavaScript(TextWriter writer, bool clientOnly = false);
+
+ ///
+ /// Returns a list of paths to scripts generated by the React app
+ ///
+ IEnumerable GetScriptPaths();
+
+ ///
+ /// Returns a list of paths to stylesheets generated by the React app
+ ///
+ IEnumerable GetStylePaths();
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/IReactIdGenerator.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/IReactIdGenerator.cs
new file mode 100644
index 00000000..57aacdae
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/IReactIdGenerator.cs
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React
+{
+ ///
+ /// Fast component ID generator
+ ///
+ public interface IReactIdGenerator
+ {
+ ///
+ /// Returns a short react identifier starts with "react_".
+ ///
+ ///
+ string Generate();
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/IReactSiteConfiguration.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/IReactSiteConfiguration.cs
new file mode 100644
index 00000000..9a19217a
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/IReactSiteConfiguration.cs
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+using Newtonsoft.Json;
+
+namespace React
+{
+ ///
+ /// Site-wide configuration for ReactJS.NET
+ ///
+ public interface IReactSiteConfiguration
+ {
+ ///
+ /// Adds a script to the list of scripts that are executed. This should be called for all
+ /// React components and their dependencies. If the script does not have any JSX in it
+ /// (for example, it's built using Webpack or Gulp), use
+ /// instead.
+ ///
+ ///
+ /// Name of the file to execute. Should be a server relative path starting with ~ (eg.
+ /// ~/Scripts/Awesome.js)
+ ///
+ /// This configuration, for chaining
+ IReactSiteConfiguration AddScript(string filename);
+
+ ///
+ /// Adds a script to the list of scripts that are executed. This is the same as
+ /// except it does not run JSX transformation on the script and thus is
+ /// more efficient.
+ ///
+ ///
+ /// Name of the file to execute. Should be a server relative path starting with ~ (eg.
+ /// ~/Scripts/Awesome.js)
+ ///
+ /// The configuration, for chaining
+ IReactSiteConfiguration AddScriptWithoutTransform(string filename);
+
+ ///
+ /// Gets a list of all the scripts that have been added to this configuration and require JSX
+ /// transformation to be run.
+ ///
+ IEnumerable Scripts { get; }
+
+ ///
+ /// Gets a list of all the scripts that have been added to this configuration and do not
+ /// require JSX transformation to be run.
+ ///
+ IEnumerable ScriptsWithoutTransform { get; }
+
+ ///
+ /// Gets or sets whether JavaScript engines should be reused across requests.
+ ///
+ ///
+ bool ReuseJavaScriptEngines { get; set; }
+ ///
+ /// Sets whether JavaScript engines should be reused across requests.
+ ///
+ IReactSiteConfiguration SetReuseJavaScriptEngines(bool value);
+
+ ///
+ /// Gets or sets the configuration for JSON serializer.
+ ///
+ JsonSerializerSettings JsonSerializerSettings { get; set; }
+
+ ///
+ /// Sets the configuration for json serializer.
+ ///
+ ///
+ /// This confiquration is used when component initialization script
+ /// is being generated server-side.
+ ///
+ /// The settings.
+ IReactSiteConfiguration SetJsonSerializerSettings(JsonSerializerSettings settings);
+
+ ///
+ /// Gets or sets the number of engines to initially start when a pool is created.
+ /// Defaults to 10.
+ ///
+ int? StartEngines { get; set; }
+ ///
+ /// Sets the number of engines to initially start when a pool is created.
+ /// Defaults to 10.
+ ///
+ IReactSiteConfiguration SetStartEngines(int? startEngines);
+
+ ///
+ /// Gets or sets the maximum number of engines that will be created in the pool.
+ /// Defaults to 25.
+ ///
+ int? MaxEngines { get; set; }
+ ///
+ /// Sets the maximum number of engines that will be created in the pool.
+ /// Defaults to 25.
+ ///
+ IReactSiteConfiguration SetMaxEngines(int? maxEngines);
+
+ ///
+ /// Gets or sets the maximum number of times an engine can be reused before it is disposed.
+ /// 0 is unlimited. Defaults to 100.
+ ///
+ int? MaxUsagesPerEngine { get; set; }
+ ///
+ /// Sets the maximum number of times an engine can be reused before it is disposed.
+ /// 0 is unlimited. Defaults to 100.
+ ///
+ IReactSiteConfiguration SetMaxUsagesPerEngine(int? maxUsagesPerEngine);
+
+ ///
+ /// Gets or sets whether to allow the JavaScript pre-compilation (accelerates the
+ /// initialization of JavaScript engines).
+ ///
+ bool AllowJavaScriptPrecompilation { get; set; }
+ ///
+ /// Sets whether to allow the JavaScript pre-compilation (accelerates the initialization of
+ /// JavaScript engines).
+ ///
+ ///
+ IReactSiteConfiguration SetAllowJavaScriptPrecompilation(bool allowJavaScriptPrecompilation);
+
+ ///
+ /// Gets or sets whether to use the debug version of React. This is slower, but gives
+ /// useful debugging tips.
+ ///
+ bool UseDebugReact { get; set; }
+ ///
+ /// Sets whether to use the debug version of React. This is slower, but gives
+ /// useful debugging tips.
+ ///
+ IReactSiteConfiguration SetUseDebugReact(bool value);
+
+ ///
+ /// Gets or sets whether server-side rendering is enabled.
+ ///
+ bool UseServerSideRendering { get; set; }
+ ///
+ /// Disables server-side rendering. This is useful when debugging your scripts.
+ ///
+ IReactSiteConfiguration DisableServerSideRendering();
+
+ ///
+ /// An exception handler which will be called if a render exception is thrown.
+ /// If unset, unhandled exceptions will be thrown for all component renders.
+ ///
+ Action ExceptionHandler { get; set; }
+
+ ///
+ /// Sets an exception handler which will be called if a render exception is thrown.
+ /// If unset, unhandled exceptions will be thrown for all component renders.
+ ///
+ ///
+ ///
+ IReactSiteConfiguration SetExceptionHandler(Action handler);
+
+ ///
+ /// A provider that returns a nonce to be used on any script tags on the page.
+ /// This value must match the nonce used in the Content Security Policy header on the response.
+ ///
+ Func ScriptNonceProvider { get; set; }
+
+ ///
+ /// Sets a provider that returns a nonce to be used on any script tags on the page.
+ /// This value must match the nonce used in the Content Security Policy header on the response.
+ ///
+ ///
+ ///
+ IReactSiteConfiguration SetScriptNonceProvider(Func provider);
+
+ ///
+ /// The path to the application bundles built by webpack or create-react-app
+ ///
+ string ReactAppBuildPath { get; set; }
+
+ ///
+ /// Sets the path to the application bundles built by webpack or create-react-app
+ ///
+ ///
+ ///
+ IReactSiteConfiguration SetReactAppBuildPath(string reactAppBuildPath);
+
+ ///
+ /// Gets or sets if the React 18+ create root api should be used for rendering / hydration.
+ /// If false ReactDOM.render / ReactDOM.hydrate will be used.
+ ///
+ bool UseRootAPI { get; set; }
+
+ ///
+ /// Enables usage of the React 18 root API when rendering / hydrating.
+ ///
+ ///
+ void EnableReact18RootAPI();
+
+ ///
+ /// If set, the styles and scripts that are emitted are filtered by this function, allowing
+ /// you to exclude Webpack bundles that should not be emitted.
+ ///
+ Func FilterResource { get; set; }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/IReactUserScriptProvider.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/IReactUserScriptProvider.cs
new file mode 100644
index 00000000..d3835157
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/IReactUserScriptProvider.cs
@@ -0,0 +1,16 @@
+namespace React.Core
+{
+ using JavaScriptEngineSwitcher.Core;
+
+ ///
+ ///
+ ///
+ public interface IReactJsEngineInitProvider
+ {
+ ///
+ ///
+ ///
+ ///
+ void InitEngine(IJsEngine engine);
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/IRenderFunctions.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/IRenderFunctions.cs
new file mode 100644
index 00000000..051d3a85
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/IRenderFunctions.cs
@@ -0,0 +1,46 @@
+namespace React
+{
+ ///
+ /// Functions to execute during a render request.
+ /// These functions will share the same Javascript context, so state can be passed around via variables.
+ ///
+ public interface IRenderFunctions
+ {
+ ///
+ /// Executes before component render.
+ /// It takes a func that accepts a Javascript code expression to evaluate, which returns the result of the expression.
+ /// This is useful for setting up variables that will be referenced after the render completes.
+ /// The func to execute
+ ///
+ void PreRender(Func executeJs);
+
+
+ ///
+ /// Transforms the React.createElement expression.
+ /// This is useful for libraries like styled components which require wrapping the root component
+ /// inside a helper to generate a stylesheet.
+ /// Example transform: React.createElement(Foo, ...) => wrapComponent(React.createElement(Foo, ...))
+ ///
+ /// The Javascript expression to wrap
+ /// A wrapped expression
+ string WrapComponent(string componentToRender);
+
+
+ ///
+ /// Transforms the compiled rendered component HTML
+ /// This is useful for libraries like emotion which take rendered component HTML and output the transformed HTML plus additional style tags
+ ///
+ /// The component HTML
+ /// A wrapped expression
+ string TransformRenderedHtml(string input);
+
+
+ ///
+ /// Executes after component render.
+ /// It takes a func that accepts a Javascript code expression to evaluate, which returns the result of the expression.
+ /// This is useful for reading computed state, such as generated stylesheets or a router redirect result.
+ ///
+ /// The func to execute
+ void PostRender(Func executeJs);
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/JavaScriptEngineFactory.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/JavaScriptEngineFactory.cs
new file mode 100644
index 00000000..e9d275ff
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/JavaScriptEngineFactory.cs
@@ -0,0 +1,389 @@
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Reflection;
+using JavaScriptEngineSwitcher.Core;
+using JSPool;
+using Microsoft.Extensions.DependencyInjection;
+using React.Core;
+using React.Exceptions;
+
+namespace React
+{
+ ///
+ /// Handles creation of JavaScript engines. All methods are thread-safe.
+ ///
+ public class JavaScriptEngineFactory : IDisposable, IJavaScriptEngineFactory
+ {
+ ///
+ /// React configuration for the current site
+ ///
+ protected readonly IReactSiteConfiguration _config;
+ ///
+ /// Cache used for storing the pre-compiled scripts
+ ///
+ protected readonly ICache _cache;
+ ///
+ /// File system wrapper
+ ///
+ protected readonly IFileSystem _fileSystem;
+ ///
+ /// Function used to create new JavaScript engine instances.
+ ///
+ protected readonly Func _factory;
+ ///
+ /// List of init providers to run before loading React scripts.
+ ///
+ private readonly IReactJsEngineInitProvider[] _initProviders;
+
+ ///
+ /// The JavaScript Engine Switcher instance used by ReactJS.NET
+ ///
+ protected readonly IJsEngineSwitcher _jsEngineSwitcher;
+ ///
+ /// Contains all current JavaScript engine instances. One per thread, keyed on thread ID.
+ ///
+ protected readonly ConcurrentDictionary _engines
+ = new ConcurrentDictionary();
+ ///
+ /// Pool of JavaScript engines to use
+ ///
+ protected IJsPool _pool;
+ ///
+ /// Whether this class has been disposed.
+ ///
+ protected bool _disposed;
+ ///
+ /// The exception that was thrown during the most recent recycle of the pool.
+ ///
+ protected Exception _scriptLoadException;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public JavaScriptEngineFactory(
+ IJsEngineSwitcher jsEngineSwitcher,
+ IReactSiteConfiguration config,
+ ICache cache,
+ IFileSystem fileSystem,
+ IServiceProvider serviceProvider
+ )
+ {
+ _jsEngineSwitcher = jsEngineSwitcher;
+ _config = config;
+ _cache = cache;
+ _fileSystem = fileSystem;
+#pragma warning disable 618
+ _factory = GetFactory(_jsEngineSwitcher);
+#pragma warning restore 618
+ _initProviders = serviceProvider.GetServices().ToArray();
+ if (_config.ReuseJavaScriptEngines)
+ {
+ _pool = CreatePool();
+ }
+ }
+
+ ///
+ /// Creates a new JavaScript engine pool.
+ ///
+ protected virtual IJsPool CreatePool()
+ {
+ var allFiles = _config.Scripts
+ .Concat(_config.ScriptsWithoutTransform)
+ .Concat(_config.ReactAppBuildPath != null
+ ? new[] { $"{_config.ReactAppBuildPath}/asset-manifest.json" }
+ : Enumerable.Empty())
+ .Select(_fileSystem.MapPath);
+
+ var poolConfig = new JsPoolConfig
+ {
+ EngineFactory = _factory,
+ Initializer = InitialiseEngine,
+ WatchPath = _fileSystem.MapPath("~/"),
+ WatchFiles = allFiles
+ };
+ if (_config.MaxEngines != null)
+ {
+ poolConfig.MaxEngines = _config.MaxEngines.Value;
+ }
+ if (_config.StartEngines != null)
+ {
+ poolConfig.StartEngines = _config.StartEngines.Value;
+ }
+ if (_config.MaxUsagesPerEngine != null)
+ {
+ poolConfig.MaxUsagesPerEngine = _config.MaxUsagesPerEngine.Value;
+ }
+
+ var pool = new JsPool(poolConfig);
+ // Reset the recycle exception on recycle. If there *are* errors loading the scripts
+ // during recycle, the errors will be caught in the initializer.
+ pool.Recycled += (sender, args) => _scriptLoadException = null;
+ return pool;
+ }
+
+ ///
+ /// Loads standard React scripts into the engine.
+ ///
+ protected virtual void InitialiseEngine(IJsEngine engine)
+ {
+ foreach (var provider in _initProviders)
+ {
+ provider.InitEngine(engine);
+ }
+
+ var thisAssembly = typeof(ReactEnvironment).GetTypeInfo().Assembly;
+ LoadResource(engine, "React.Core.Resources.shims.js", thisAssembly);
+
+ LoadUserScripts(engine);
+ if (_scriptLoadException == null)
+ {
+ // We expect the user to have loaded their own version of React in the scripts that
+ // were loaded above, let's ensure that's the case.
+ EnsureReactLoaded(engine);
+ }
+ }
+
+ ///
+ /// Loads code from embedded JavaScript resource into the engine.
+ ///
+ /// Engine to load a code from embedded JavaScript resource
+ /// The case-sensitive resource name
+ /// The assembly, which contains the embedded resource
+ private void LoadResource(IJsEngine engine, string resourceName, Assembly assembly)
+ {
+ if (_config.AllowJavaScriptPrecompilation
+ && engine.TryExecuteResourceWithPrecompilation(_cache, resourceName, assembly))
+ {
+ // Do nothing.
+ }
+ else
+ {
+ engine.ExecuteResource(resourceName, assembly);
+ }
+ }
+
+ ///
+ /// Loads any user-provided scripts. Only scripts that don't need JSX transformation can
+ /// run immediately here. JSX files are loaded in ReactEnvironment.
+ ///
+ /// Engine to load scripts into
+ private void LoadUserScripts(IJsEngine engine)
+ {
+ try
+ {
+ IEnumerable manifestFiles = Enumerable.Empty();
+ if (_config.ReactAppBuildPath != null)
+ {
+ var manifest = ReactAppAssetManifest.LoadManifest(_config, _fileSystem, _cache, useCacheRead: false);
+ manifestFiles = (manifest?.Entrypoints?.Where(x => x != null && x.EndsWith(".js"))) ?? Enumerable.Empty();
+ }
+
+ foreach (var file in _config.ScriptsWithoutTransform.Concat(manifestFiles))
+ {
+ try
+ {
+ if (_config.AllowJavaScriptPrecompilation
+ && engine.TryExecuteFileWithPrecompilation(_cache, _fileSystem, file))
+ {
+ // Do nothing.
+ }
+ else
+ {
+ engine.ExecuteFile(_fileSystem, file);
+ }
+ }
+ catch (JsException ex)
+ {
+ // We can't simply rethrow the exception here, as it's possible this is running
+ // on a background thread (ie. as a response to a file changing). If we did
+ // throw the exception here, it would terminate the entire process. Instead,
+ // save the exception, and then just rethrow it later when getting the engine.
+ _scriptLoadException = new ReactScriptLoadException(string.Format(
+ "Error while loading \"{0}\": {1}",
+ file,
+ ex.Message
+ ), ex);
+ }
+ }
+ }
+ catch (IOException ex)
+ {
+ // Files could be in the process of being rebuilt by JS build tooling
+ _scriptLoadException = new ReactScriptLoadException(ex.Message, ex); ;
+ }
+ }
+
+ ///
+ /// Ensures that React has been correctly loaded into the specified engine.
+ ///
+ /// Engine to check
+ private void EnsureReactLoaded(IJsEngine engine)
+ {
+ var globalsString = engine.CallFunction("ReactNET_initReact");
+ string[] globals = globalsString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+
+ if (globals.Length != 0)
+ {
+ _scriptLoadException = new ReactNotInitialisedException(
+ $"React has not been loaded correctly: missing ({string.Join(", ", globals)})." +
+ "Please expose your version of React as global variables named " +
+ "'React', 'ReactDOM', and 'ReactDOMServer', or enable the 'LoadReact'" +
+ "configuration option to use the built-in version of React."
+ );
+ }
+ }
+
+ ///
+ /// Gets the JavaScript engine for the current thread. It is recommended to use
+ /// instead, which will pool/reuse engines.
+ ///
+ /// The JavaScript engine
+ public virtual IJsEngine GetEngineForCurrentThread()
+ {
+ EnsureValidState();
+ return _engines.GetOrAdd(Thread.CurrentThread.ManagedThreadId, id =>
+ {
+ var engine = _factory();
+ InitialiseEngine(engine);
+ EnsureValidState();
+ return engine;
+ });
+ }
+
+ ///
+ /// Disposes the JavaScript engine for the current thread.
+ ///
+ public virtual void DisposeEngineForCurrentThread()
+ {
+ IJsEngine engine;
+ if (_engines.TryRemove(Thread.CurrentThread.ManagedThreadId, out engine))
+ {
+ if (engine != null)
+ {
+ engine.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// Gets a JavaScript engine from the pool.
+ ///
+ /// The JavaScript engine
+ public virtual PooledJsEngine GetEngine()
+ {
+ EnsureValidState();
+ return _pool.GetEngine();
+ }
+
+ ///
+ /// Gets a factory for the most appropriate JavaScript engine for the current environment.
+ /// The first functioning JavaScript engine with the lowest priority will be used.
+ ///
+ /// Function to create JavaScript engine
+ private static Func GetFactory(IJsEngineSwitcher jsEngineSwitcher)
+ {
+ string defaultEngineName = jsEngineSwitcher.DefaultEngineName;
+ if (!string.IsNullOrWhiteSpace(defaultEngineName))
+ {
+ var engineFactory = jsEngineSwitcher.EngineFactories.Get(defaultEngineName);
+ if (engineFactory != null)
+ {
+ return engineFactory.CreateEngine;
+ }
+ else
+ {
+ throw new ReactEngineNotFoundException(
+ "Could not find a factory that creates an instance of the JavaScript " +
+ "engine with name `" + defaultEngineName + "`.");
+ }
+ }
+
+ if (jsEngineSwitcher.EngineFactories.Count == 0)
+ {
+ throw new ReactException("No JS engines were registered. Visit https://reactjs.net/docs for more information.");
+ }
+
+ var exceptionMessages = new List();
+ foreach (var engineFactory in jsEngineSwitcher.EngineFactories.GetRegisteredFactories())
+ {
+ IJsEngine engine = null;
+ try
+ {
+ engine = engineFactory.CreateEngine();
+ if (EngineIsUsable(engine))
+ {
+ // Success! Use this one.
+ return engineFactory.CreateEngine;
+ }
+ }
+ catch (JsEngineLoadException ex)
+ {
+ Trace.WriteLine(string.Format("Error initialising {0}: {1}", engineFactory, ex.Message));
+ exceptionMessages.Add(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine(string.Format("Error initialising {0}: {1}", engineFactory, ex));
+ exceptionMessages.Add(ex.ToString());
+ }
+ finally
+ {
+ if (engine != null)
+ {
+ engine.Dispose();
+ }
+ }
+ }
+
+ throw new ReactEngineNotFoundException("There was an error initializing the registered JS engines. " + string.Join(Environment.NewLine, exceptionMessages));
+ }
+
+ ///
+ /// Performs a sanity check to ensure the specified engine type is usable.
+ ///
+ /// Engine to test
+ ///
+ private static bool EngineIsUsable(IJsEngine engine)
+ {
+ // Perform a sanity test to ensure this engine is usable
+ return engine.Evaluate("1 + 1") == 2;
+ }
+
+ ///
+ /// Clean up all engines
+ ///
+ public virtual void Dispose()
+ {
+ _disposed = true;
+ foreach (var engine in _engines)
+ {
+ if (engine.Value != null)
+ {
+ engine.Value.Dispose();
+ }
+ }
+ if (_pool != null)
+ {
+ _pool.Dispose();
+ _pool = null;
+ }
+ }
+
+ ///
+ /// Ensures that this object has not been disposed, and that no error was thrown while
+ /// loading the scripts.
+ ///
+ public void EnsureValidState()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(GetType().Name);
+ }
+ if (_scriptLoadException != null)
+ {
+ // This means an exception occurred while loading the script (eg. syntax error in the file)
+ throw _scriptLoadException;
+ }
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/JavaScriptEnginePrecompilationUtils.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/JavaScriptEnginePrecompilationUtils.cs
new file mode 100644
index 00000000..aff35e2b
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/JavaScriptEnginePrecompilationUtils.cs
@@ -0,0 +1,116 @@
+using System.Reflection;
+using JavaScriptEngineSwitcher.Core;
+using React.Exceptions;
+
+namespace React
+{
+ ///
+ /// Helper methods for pre-compilation features of the JavaScript engine environment.
+ ///
+ public static class JavaScriptEnginePrecompilationUtils
+ {
+ ///
+ /// Cache key for the script resource pre-compilation
+ ///
+ private const string PRECOMPILED_JS_RESOURCE_CACHE_KEY = "PRECOMPILED_JS_RESOURCE_{0}";
+ ///
+ /// Cache key for the script file pre-compilation
+ ///
+ private const string PRECOMPILED_JS_FILE_CACHE_KEY = "PRECOMPILED_JS_FILE_{0}";
+ ///
+ /// Value that indicates whether a cache entry, that contains a precompiled script, should be
+ /// evicted if it has not been accessed in a given span of time
+ ///
+ private readonly static TimeSpan PRECOMPILED_JS_CACHE_ENTRY_SLIDING_EXPIRATION = TimeSpan.FromMinutes(30);
+
+ ///
+ /// Tries to execute a code from JavaScript file with pre-compilation.
+ ///
+ /// Engine to execute code from JavaScript file with pre-compilation
+ /// Cache used for storing the pre-compiled scripts
+ /// File system wrapper
+ /// Path to the JavaScript file
+ /// Delegate that loads a code from specified JavaScript file
+ /// true if can perform a script pre-compilation; otherwise, false.
+ public static bool TryExecuteFileWithPrecompilation(this IJsEngine engine, ICache cache,
+ IFileSystem fileSystem, string path, Func scriptLoader = null)
+ {
+ EnsurePrecompilationAvailability(engine, cache);
+
+ var cacheKey = string.Format(PRECOMPILED_JS_FILE_CACHE_KEY, path);
+ var precompiledScript = cache.Get(cacheKey);
+
+ if (precompiledScript == null)
+ {
+ var contents = scriptLoader != null ? scriptLoader(path) : fileSystem.ReadAsString(path);
+ precompiledScript = engine.Precompile(contents, path);
+ var fullPath = fileSystem.MapPath(path);
+ cache.Set(
+ cacheKey,
+ precompiledScript,
+ slidingExpiration: PRECOMPILED_JS_CACHE_ENTRY_SLIDING_EXPIRATION,
+ cacheDependencyFiles: new[] { fullPath }
+ );
+ }
+
+ engine.Execute(precompiledScript);
+
+ return true;
+ }
+
+ ///
+ /// Tries to execute a code from embedded JavaScript resource with pre-compilation.
+ ///
+ /// Engine to execute a code from embedded JavaScript resource with pre-compilation
+ /// Cache used for storing the pre-compiled scripts
+ /// The case-sensitive resource name
+ /// The assembly, which contains the embedded resource
+ /// true if can perform a script pre-compilation; otherwise, false.
+ public static bool TryExecuteResourceWithPrecompilation(this IJsEngine engine, ICache cache,
+ string resourceName, Assembly assembly)
+ {
+ EnsurePrecompilationAvailability(engine, cache);
+
+ var cacheKey = string.Format(PRECOMPILED_JS_RESOURCE_CACHE_KEY, resourceName);
+ var precompiledScript = cache.Get(cacheKey);
+
+ if (precompiledScript == null)
+ {
+ precompiledScript = engine.PrecompileResource(resourceName, assembly);
+ cache.Set(
+ cacheKey,
+ precompiledScript,
+ slidingExpiration: PRECOMPILED_JS_CACHE_ENTRY_SLIDING_EXPIRATION
+ );
+ }
+
+ engine.Execute(precompiledScript);
+
+ return true;
+ }
+
+ ///
+ /// Ensures that the script pre-compilation is available.
+ ///
+ /// Instance of the JavaScript engine
+ /// Cache used for storing the pre-compiled scripts
+ private static void EnsurePrecompilationAvailability(IJsEngine engine, ICache cache)
+ {
+ if (!engine.SupportsScriptPrecompilation)
+ {
+ throw new ReactScriptPrecompilationNotAvailableException(string.Format(
+ "The {0} version {1} does not support the script pre-compilation.",
+ engine.Name,
+ engine.Version
+ ));
+ }
+
+ if (cache is NullCache)
+ {
+ throw new ReactScriptPrecompilationNotAvailableException(string.Format(
+ "Usage of the script pre-compilation without caching does not make sense."
+ ));
+ }
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/JavaScriptEngineUtils.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/JavaScriptEngineUtils.cs
new file mode 100644
index 00000000..dd945658
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/JavaScriptEngineUtils.cs
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+using JavaScriptEngineSwitcher.Core;
+using JavaScriptEngineSwitcher.Core.Helpers;
+using Newtonsoft.Json;
+using React.Exceptions;
+
+namespace React
+{
+ ///
+ /// Various helper methods for the JavaScript engine environment.
+ ///
+ public static class JavaScriptEngineUtils
+ {
+ ///
+ /// Executes a code from JavaScript file.
+ ///
+ /// Engine to execute code from JavaScript file
+ /// File system wrapper
+ /// Path to the JavaScript file
+ public static void ExecuteFile(this IJsEngine engine, IFileSystem fileSystem, string path)
+ {
+ var contents = fileSystem.ReadAsString(path);
+ engine.Execute(contents, path);
+ }
+
+ ///
+ /// Calls a JavaScript function using the specified engine. If is
+ /// not a scalar type, the function is assumed to return a string of JSON that can be
+ /// parsed as that type.
+ ///
+ /// Type returned by function
+ /// Engine to execute function with
+ /// Name of the function to execute
+ /// Arguments to pass to function
+ /// Value returned by function
+ public static T CallFunctionReturningJson(this IJsEngine engine, string function, params object[] args)
+ {
+ if (ValidationHelpers.IsSupportedType(typeof(T)))
+ {
+ // Type is supported directly (ie. a scalar type like string/int/bool)
+ // Just execute the function directly.
+ return engine.CallFunction(function, args);
+ }
+ // The type is not a scalar type. Assume the function will return its result as
+ // JSON.
+ var resultJson = engine.CallFunction(function, args);
+ try
+ {
+ return JsonConvert.DeserializeObject(resultJson);
+ }
+ catch (JsonReaderException ex)
+ {
+ throw new ReactException(string.Format(
+ "{0} did not return valid JSON: {1}.\n\n{2}",
+ function, ex.Message, resultJson
+ ), ex);
+ }
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/JavaScriptWithSourceMap.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/JavaScriptWithSourceMap.cs
new file mode 100644
index 00000000..0bee3a5e
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/JavaScriptWithSourceMap.cs
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React
+{
+ ///
+ /// Represents the result of a Babel transformation along with its
+ /// corresponding source map.
+ ///
+ [Serializable]
+ public class JavaScriptWithSourceMap
+ {
+ ///
+ /// The transformed result
+ ///
+ public string Code { get; set; }
+
+ ///
+ /// The hash of the input file.
+ ///
+ public string Hash { get; set; }
+
+ ///
+ /// The source map for this code
+ ///
+ public SourceMap SourceMap { get; set; }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/NullCache.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/NullCache.cs
new file mode 100644
index 00000000..8b7d5c6c
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/NullCache.cs
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React
+{
+ ///
+ /// Implementation of that never caches.
+ ///
+ public class NullCache : ICache
+ {
+ ///
+ /// Get an item from the cache. Returns if the item does
+ /// not exist.
+ ///
+ /// Type of data
+ /// The cache key
+ /// Value to return if item is not in the cache
+ /// Data from cache, otherwise
+ public T Get(string key, T fallback = default(T))
+ {
+ return fallback;
+ }
+
+ ///
+ /// Sets an item in the cache.
+ ///
+ /// Type of data
+ /// The cache key
+ /// Data to cache
+ ///
+ /// Sliding expiration, if cache key is not accessed in this time period it will
+ /// automatically be removed from the cache
+ ///
+ ///
+ /// Filenames this cached item is dependent on. If any of these files change, the cache
+ /// will be cleared automatically
+ ///
+ public void Set(string key, T data, TimeSpan slidingExpiration, IEnumerable cacheDependencyFiles = null)
+ {
+ // no-op
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/README.md b/UET/Lib/Redpoint.ThirdParty.React.Core/README.md
new file mode 100644
index 00000000..b6d307a7
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/README.md
@@ -0,0 +1,5 @@
+# Redpoint.ThirdParty.React.AspNet
+
+This is a fork of React.NET (https://github.com/reactjs/react.net) that adds support for React v18.
+
+If you want to use this fork in an ASP.NET Core project, use the `Redpoint.ThirdParty.React.AspNet` package.
\ No newline at end of file
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/ReactAppAssetManifest.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/ReactAppAssetManifest.cs
new file mode 100644
index 00000000..94d86c36
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/ReactAppAssetManifest.cs
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+using Newtonsoft.Json;
+
+namespace React
+{
+ internal class ReactAppAssetManifest
+ {
+ public Dictionary Files { get; set; }
+ public List Entrypoints { get; set; }
+
+ public static ReactAppAssetManifest LoadManifest(IReactSiteConfiguration config, IFileSystem fileSystem, ICache cache, bool useCacheRead)
+ {
+ string cacheKey = "REACT_APP_MANIFEST";
+
+ if (useCacheRead)
+ {
+ var cachedManifest = cache.Get(cacheKey);
+ if (cachedManifest != null)
+ return cachedManifest;
+ }
+
+ var manifestString = fileSystem.ReadAsString($"{config.ReactAppBuildPath}/asset-manifest.json");
+ var manifest = JsonConvert.DeserializeObject(manifestString);
+
+ cache.Set(cacheKey, manifest, TimeSpan.FromHours(1));
+ return manifest;
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/ReactComponent.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/ReactComponent.cs
new file mode 100644
index 00000000..485fdff9
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/ReactComponent.cs
@@ -0,0 +1,363 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+using System.Collections.Concurrent;
+using System.Text;
+using System.Text.RegularExpressions;
+using JavaScriptEngineSwitcher.Core;
+using Newtonsoft.Json;
+using React.Exceptions;
+
+namespace React
+{
+ ///
+ /// Represents a React JavaScript component.
+ ///
+ public class ReactComponent : IReactComponent
+ {
+ private static readonly ConcurrentDictionary _componentNameValidCache = new ConcurrentDictionary(StringComparer.Ordinal);
+
+ [ThreadStatic]
+ private static StringWriter _sharedStringWriter;
+
+ ///
+ /// Regular expression used to validate JavaScript identifiers. Used to ensure component
+ /// names are valid.
+ /// Based off https://gist.github.com/Daniel15/3074365
+ ///
+ private static readonly Regex _identifierRegex = new Regex(@"^[a-zA-Z_$][0-9a-zA-Z_$]*(?:\[(?:"".+""|\'.+\'|\d+)\])*?$", RegexOptions.Compiled);
+
+ ///
+ /// Environment this component has been created in
+ ///
+ protected readonly IReactEnvironment _environment;
+
+ ///
+ /// Global site configuration
+ ///
+ protected readonly IReactSiteConfiguration _configuration;
+
+ ///
+ /// Raw props for this component
+ ///
+ protected object _props;
+
+ ///
+ /// JSON serialized props for this component
+ ///
+ protected string _serializedProps;
+
+ ///
+ /// Gets or sets the name of the component
+ ///
+ public string ComponentName { get; set; }
+
+ ///
+ /// Gets or sets the unique ID for the DIV container of this component
+ ///
+ public string ContainerId { get; set; }
+
+ ///
+ /// Gets or sets the HTML tag the component is wrapped in
+ ///
+ public string ContainerTag { get; set; }
+
+ ///
+ /// Gets or sets the HTML class for the container of this component
+ ///
+ public string ContainerClass { get; set; }
+
+ ///
+ /// Get or sets if this components only should be rendered server side
+ ///
+ public bool ServerOnly { get; set; }
+
+ ///
+ /// Gets or sets the props for this component
+ ///
+ public object Props
+ {
+ get { return _props; }
+ set
+ {
+ _props = value;
+ _serializedProps = JsonConvert.SerializeObject(
+ value,
+ _configuration.JsonSerializerSettings);
+ }
+ }
+ ///
+ /// Get or sets if this components only should be rendered client side
+ ///
+ public bool ClientOnly { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The environment.
+ /// Site-wide configuration.
+ /// React Id generator.
+ /// Name of the component.
+ /// The ID of the container DIV for this component
+ public ReactComponent(IReactEnvironment environment, IReactSiteConfiguration configuration, IReactIdGenerator reactIdGenerator, string componentName, string containerId)
+ {
+ EnsureComponentNameValid(componentName);
+ _environment = environment;
+ _configuration = configuration;
+ ComponentName = componentName;
+ ContainerId = string.IsNullOrEmpty(containerId) ? reactIdGenerator.Generate() : containerId;
+ ContainerTag = "div";
+ }
+
+ ///
+ /// Renders the HTML for this component. This will execute the component server-side and
+ /// return the rendered HTML.
+ ///
+ /// Only renders component container. Used for client-side only rendering.
+ /// Only renders the common HTML mark up and not any React specific data attributes. Used for server-side only rendering.
+ /// A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)
+ /// Functions to call during component render
+ /// HTML
+ public virtual string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false, Action exceptionHandler = null, IRenderFunctions renderFunctions = null)
+ {
+ return GetStringFromWriter(renderHtmlWriter => RenderHtml(renderHtmlWriter, renderContainerOnly, renderServerOnly, exceptionHandler, renderFunctions));
+ }
+
+ ///
+ /// Renders the HTML for this component. This will execute the component server-side and
+ /// return the rendered HTML.
+ ///
+ /// The to which the content is written
+ /// Only renders component container. Used for client-side only rendering.
+ /// Only renders the common HTML mark up and not any React specific data attributes. Used for server-side only rendering.
+ /// A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)
+ /// Functions to call during component render
+ /// HTML
+ public virtual void RenderHtml(TextWriter writer, bool renderContainerOnly = false, bool renderServerOnly = false, Action exceptionHandler = null, IRenderFunctions renderFunctions = null)
+ {
+ if (!_configuration.UseServerSideRendering)
+ {
+ renderContainerOnly = true;
+ }
+
+ if (!renderContainerOnly)
+ {
+ EnsureComponentExists();
+ }
+
+ var html = string.Empty;
+ if (!renderContainerOnly)
+ {
+ var stringWriter = _sharedStringWriter;
+ if (stringWriter != null)
+ {
+ stringWriter.GetStringBuilder().Clear();
+ }
+ else
+ {
+ _sharedStringWriter = stringWriter = new StringWriter(new StringBuilder(_serializedProps.Length + 128));
+ }
+
+ try
+ {
+ stringWriter.Write(renderServerOnly ? "ReactDOMServer.renderToStaticMarkup(" : "ReactDOMServer.renderToString(");
+ if (renderFunctions != null)
+ {
+ stringWriter.Write(renderFunctions.WrapComponent(GetStringFromWriter(componentInitWriter => WriteComponentInitialiser(componentInitWriter))));
+ }
+ else
+ {
+ WriteComponentInitialiser(stringWriter);
+ }
+ stringWriter.Write(')');
+
+ if (renderFunctions != null)
+ {
+ renderFunctions.PreRender(x => _environment.Execute(x));
+ html = _environment.Execute(renderFunctions.TransformRenderedHtml(stringWriter.ToString()));
+ renderFunctions.PostRender(x => _environment.Execute(x));
+ }
+ else
+ {
+ html = _environment.Execute(stringWriter.ToString());
+ }
+
+ if (renderServerOnly)
+ {
+ writer.Write(html);
+ return;
+ }
+ }
+ catch (JsException ex)
+ {
+ if (exceptionHandler == null)
+ {
+ exceptionHandler = _configuration.ExceptionHandler;
+ }
+
+ exceptionHandler(ex, ComponentName, ContainerId);
+ }
+ }
+
+ writer.Write('<');
+ writer.Write(ContainerTag);
+ writer.Write(" id=\"");
+ writer.Write(ContainerId);
+ writer.Write('"');
+ if (!string.IsNullOrEmpty(ContainerClass))
+ {
+ writer.Write(" class=\"");
+ writer.Write(ContainerClass);
+ writer.Write('"');
+ }
+
+ writer.Write('>');
+ writer.Write(html);
+ writer.Write("");
+ writer.Write(ContainerTag);
+ writer.Write('>');
+ }
+
+ ///
+ /// Renders the JavaScript required to initialise this component client-side. This will
+ /// initialise the React component, which includes attach event handlers to the
+ /// server-rendered HTML.
+ ///
+ /// JavaScript
+ public virtual string RenderJavaScript(bool waitForDOMContentLoad)
+ {
+ return GetStringFromWriter(renderJsWriter => RenderJavaScript(renderJsWriter, waitForDOMContentLoad));
+ }
+
+ ///
+ /// Renders the JavaScript required to initialise this component client-side. This will
+ /// initialise the React component, which includes attach event handlers to the
+ /// server-rendered HTML.
+ ///
+ /// The to which the content is written
+ /// Delays the component init until the page load event fires. Useful if the component script tags are located after the call to Html.ReactWithInit.
+ /// JavaScript
+ public virtual void RenderJavaScript(TextWriter writer, bool waitForDOMContentLoad)
+ {
+ if (waitForDOMContentLoad)
+ {
+ writer.Write("window.addEventListener('DOMContentLoaded', function() {");
+ }
+
+ if (_configuration.UseRootAPI)
+ {
+ WriteComponentInitialization(writer);
+ }
+ else
+ {
+ WriteLegacyComponentInitialization(writer);
+ }
+
+ if (waitForDOMContentLoad)
+ {
+ writer.Write("});");
+ }
+ }
+
+ ///
+ /// Writes initialization code using the React 18 root API
+ ///
+ private void WriteComponentInitialization(TextWriter writer)
+ {
+ var hydrate = _configuration.UseServerSideRendering && !ClientOnly;
+ if (hydrate)
+ {
+ writer.Write("ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.usingClientEntryPoint = true;");
+ writer.Write("ReactDOM.hydrateRoot(");
+ writer.Write("document.getElementById(\"");
+ writer.Write(ContainerId);
+ writer.Write("\")");
+ writer.Write(", ");
+ WriteComponentInitialiser(writer);
+ writer.Write(")");
+ }
+ else
+ {
+ writer.Write("ReactDOM.createRoot(");
+ writer.Write("document.getElementById(\"");
+ writer.Write(ContainerId);
+ writer.Write("\"))");
+ writer.Write(".render(");
+ WriteComponentInitialiser(writer);
+ writer.Write(")");
+ }
+ }
+
+ ///
+ /// Writes initialization code using the old ReactDOM.render / ReactDOM.hydrate APIs.
+ ///
+ private void WriteLegacyComponentInitialization(TextWriter writer)
+ {
+ writer.Write(
+ !_configuration.UseServerSideRendering || ClientOnly ? "ReactDOM.render(" : "ReactDOM.hydrate(");
+ WriteComponentInitialiser(writer);
+ writer.Write(", document.getElementById(\"");
+ writer.Write(ContainerId);
+ writer.Write("\"))");
+ }
+
+ ///
+ /// Ensures that this component exists in global scope
+ ///
+ protected virtual void EnsureComponentExists()
+ {
+ // This is safe as componentName was validated via EnsureComponentNameValid()
+ var componentExists = _environment.Execute(string.Format(
+ "typeof {0} !== 'undefined'",
+ ComponentName
+ ));
+ if (!componentExists)
+ {
+ throw new ReactInvalidComponentException(string.Format(
+ "Could not find a component named '{0}'. Did you forget to add it to " +
+ "App_Start\\ReactConfig.cs?",
+ ComponentName
+ ));
+ }
+ }
+
+ ///
+ /// Gets the JavaScript code to initialise the component
+ ///
+ /// The to which the content is written
+ protected virtual void WriteComponentInitialiser(TextWriter writer)
+ {
+ writer.Write("React.createElement(");
+ writer.Write(ComponentName);
+ writer.Write(", ");
+ writer.Write(_serializedProps);
+ writer.Write(')');
+ }
+
+ ///
+ /// Validates that the specified component name is valid
+ ///
+ ///
+ internal static void EnsureComponentNameValid(string componentName)
+ {
+ var isValid = _componentNameValidCache.GetOrAdd(componentName, compName => compName.Split('.').All(segment => _identifierRegex.IsMatch(segment)));
+ if (!isValid)
+ {
+ throw new ReactInvalidComponentException($"Invalid component name '{componentName}'");
+ }
+ }
+
+ private string GetStringFromWriter(Action fnWithTextWriter)
+ {
+ using (var textWriter = new StringWriter())
+ {
+ fnWithTextWriter(textWriter);
+ return textWriter.ToString();
+ }
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/ReactEnvironment.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/ReactEnvironment.cs
new file mode 100644
index 00000000..4710a17b
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/ReactEnvironment.cs
@@ -0,0 +1,332 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+using System.Reflection;
+using JavaScriptEngineSwitcher.Core;
+using JSPool;
+
+namespace React
+{
+ ///
+ /// Request-specific ReactJS.NET environment. This is unique to the individual request and is
+ /// not shared.
+ ///
+ public class ReactEnvironment : IReactEnvironment, IDisposable
+ {
+ ///
+ /// JavaScript variable set when user-provided scripts have been loaded
+ ///
+ protected const string USER_SCRIPTS_LOADED_KEY = "_ReactNET_UserScripts_Loaded";
+ ///
+ /// Stack size to use for JSXTransformer if the default stack is insufficient
+ ///
+ protected const int LARGE_STACK_SIZE = 2 * 1024 * 1024;
+
+ ///
+ /// Factory to create JavaScript engines
+ ///
+ protected readonly IJavaScriptEngineFactory _engineFactory;
+ ///
+ /// Site-wide configuration
+ ///
+ protected readonly IReactSiteConfiguration _config;
+ ///
+ /// Cache used for storing compiled JSX
+ ///
+ protected readonly ICache _cache;
+ ///
+ /// File system wrapper
+ ///
+ protected readonly IFileSystem _fileSystem;
+ ///
+ /// Hash algorithm for file-based cache
+ ///
+ protected readonly IFileCacheHash _fileCacheHash;
+ ///
+ /// React Id generator
+ ///
+ private readonly IReactIdGenerator _reactIdGenerator;
+
+ ///
+ /// Version number of ReactJS.NET
+ ///
+ protected readonly Lazy _version = new Lazy(GetVersion);
+
+ ///
+ /// Contains an engine acquired from a pool of engines. Only used if
+ /// is enabled.
+ ///
+ protected Lazy _engineFromPool;
+
+ ///
+ /// List of all components instantiated in this environment
+ ///
+ protected readonly IList _components = new List();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The JavaScript engine factory
+ /// The site-wide configuration
+ /// The cache to use for JSX compilation
+ /// File system wrapper
+ /// Hash algorithm for file-based cache
+ /// React ID generator
+ public ReactEnvironment(
+ IJavaScriptEngineFactory engineFactory,
+ IReactSiteConfiguration config,
+ ICache cache,
+ IFileSystem fileSystem,
+ IFileCacheHash fileCacheHash,
+ IReactIdGenerator reactIdGenerator)
+ {
+ _engineFactory = engineFactory;
+ _config = config;
+ _cache = cache;
+ _fileSystem = fileSystem;
+ _fileCacheHash = fileCacheHash;
+ _reactIdGenerator = reactIdGenerator;
+ _engineFromPool = new Lazy(() => _engineFactory.GetEngine());
+ }
+
+ ///
+ /// Gets the JavaScript engine to use for this environment.
+ ///
+ protected virtual IJsEngine Engine
+ {
+ get
+ {
+ return _config.ReuseJavaScriptEngines
+ ? _engineFromPool.Value
+ : _engineFactory.GetEngineForCurrentThread();
+ }
+ }
+
+ ///
+ /// Gets the version of the JavaScript engine in use by ReactJS.NET
+ ///
+ public virtual string EngineVersion
+ {
+ get { return Engine.Name + " " + Engine.Version; }
+ }
+
+ ///
+ /// Gets the version number of ReactJS.NET
+ ///
+ public virtual string Version
+ {
+ get { return _version.Value; }
+ }
+
+ ///
+ /// Ensures any user-provided scripts have been loaded. This only loads JSX files; files
+ /// that need no transformation are loaded in JavaScriptEngineFactory.
+ ///
+ protected virtual void EnsureUserScriptsLoaded()
+ {
+ // We no longer do Babel transpilation.
+ Engine.SetVariableValue(USER_SCRIPTS_LOADED_KEY, true);
+ return;
+ }
+
+ ///
+ /// Executes the provided JavaScript code.
+ ///
+ /// JavaScript to execute
+ public virtual void Execute(string code)
+ {
+ Engine.Execute(code);
+ }
+
+ ///
+ /// Executes the provided JavaScript code, returning a result of the specified type.
+ ///
+ /// Type to return
+ /// Code to execute
+ /// Result of the JavaScript code
+ public virtual T Execute(string code)
+ {
+ return Engine.Evaluate(code);
+ }
+
+ ///
+ /// Executes the provided JavaScript function, returning a result of the specified type.
+ ///
+ /// Type to return
+ /// JavaScript function to execute
+ /// Arguments to pass to function
+ /// Result of the JavaScript code
+ public virtual T Execute(string function, params object[] args)
+ {
+ return Engine.CallFunctionReturningJson(function, args);
+ }
+
+ ///
+ /// Determines if the specified variable exists in the JavaScript engine
+ ///
+ /// Name of the variable
+ /// true if the variable exists; false otherwise
+ public virtual bool HasVariable(string name)
+ {
+ return Engine.HasVariable(name);
+ }
+
+ ///
+ /// Creates an instance of the specified React JavaScript component.
+ ///
+ /// Type of the props
+ /// Name of the component
+ /// Props to use
+ /// ID to use for the container HTML tag. Defaults to an auto-generated ID
+ /// True if server-side rendering will be bypassed. Defaults to false.
+ /// True if this component only should be rendered server-side. Defaults to false.
+ /// Skip adding to components list, which is used during GetInitJavascript
+ /// The component
+ public virtual IReactComponent CreateComponent(string componentName, T props, string containerId = null, bool clientOnly = false, bool serverOnly = false, bool skipLazyInit = false)
+ {
+ if (!clientOnly)
+ {
+ EnsureUserScriptsLoaded();
+ }
+
+ var component = new ReactComponent(this, _config, _reactIdGenerator, componentName, containerId)
+ {
+ ClientOnly = clientOnly,
+ Props = props,
+ ServerOnly = serverOnly
+ };
+
+ if (!skipLazyInit)
+ {
+ _components.Add(component);
+ }
+ return component;
+ }
+
+ ///
+ /// Adds the provided to the list of components to render client side.
+ ///
+ /// Component to add to client side render list
+ /// True if server-side rendering will be bypassed. Defaults to false.
+ /// The component
+ public virtual IReactComponent CreateComponent(IReactComponent component, bool clientOnly = false)
+ {
+ if (!clientOnly)
+ {
+ EnsureUserScriptsLoaded();
+ }
+
+ _components.Add(component);
+ return component;
+ }
+
+ ///
+ /// Renders the JavaScript required to initialise all components client-side. This will
+ /// attach event handlers to the server-rendered HTML.
+ ///
+ /// True if server-side rendering will be bypassed. Defaults to false.
+ /// JavaScript for all components
+ public virtual string GetInitJavaScript(bool clientOnly = false)
+ {
+ using (var writer = new StringWriter())
+ {
+ GetInitJavaScript(writer, clientOnly);
+ return writer.ToString();
+ }
+ }
+
+ ///
+ /// Renders the JavaScript required to initialise all components client-side. This will
+ /// attach event handlers to the server-rendered HTML.
+ ///
+ /// The to which the content is written
+ /// True if server-side rendering will be bypassed. Defaults to false.
+ /// JavaScript for all components
+ public virtual void GetInitJavaScript(TextWriter writer, bool clientOnly = false)
+ {
+ // Propagate any server-side console.log calls to corresponding client-side calls.
+ if (!clientOnly && _components.Count != 0)
+ {
+ var consoleCalls = Execute("console.getCalls()");
+ writer.Write(consoleCalls);
+ }
+
+ foreach (var component in _components)
+ {
+ if (!component.ServerOnly)
+ {
+ component.RenderJavaScript(writer, waitForDOMContentLoad: false);
+ writer.WriteLine(';');
+ }
+ }
+ }
+
+ private ReactAppAssetManifest GetAppManifest() => ReactAppAssetManifest.LoadManifest(_config, _fileSystem, _cache, useCacheRead: true);
+
+ ///
+ /// Returns a list of paths to scripts generated by the React app
+ ///
+ public virtual IEnumerable GetScriptPaths()
+ {
+ return GetAppManifest().Entrypoints
+ .Where(path => path.EndsWith(".js"))
+ .Where(path => _config.FilterResource == null || _config.FilterResource(path));
+ }
+
+ ///
+ /// Returns a list of paths to stylesheets generated by the React app
+ ///
+ public virtual IEnumerable GetStylePaths()
+ {
+ return GetAppManifest().Entrypoints
+ .Where(path => path.EndsWith(".css"))
+ .Where(path => _config.FilterResource == null || _config.FilterResource(path));
+ }
+
+ ///
+ /// Gets the ReactJS.NET version number. Use instead.
+ ///
+ private static string GetVersion()
+ {
+ var assembly = typeof(ReactEnvironment).GetTypeInfo().Assembly;
+ var rawVersion = assembly.GetCustomAttribute().Version;
+ var lastDot = rawVersion.LastIndexOf('.');
+ var version = rawVersion.Substring(0, lastDot);
+ var build = rawVersion.Substring(lastDot + 1);
+ return string.Format("{0} (build {1})", version, build);
+ }
+
+ ///
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ public virtual void Dispose()
+ {
+ _engineFactory.DisposeEngineForCurrentThread();
+ ReturnEngineToPool();
+ }
+
+ ///
+ /// Returns the currently held JS engine to the pool. (no-op if engine pooling is disabled)
+ ///
+ public void ReturnEngineToPool()
+ {
+ if (_engineFromPool.IsValueCreated)
+ {
+ _engineFromPool.Value.Dispose();
+ _engineFromPool = new Lazy(() => _engineFactory.GetEngine());
+ }
+ }
+
+ ///
+ /// Gets the site-wide configuration.
+ ///
+ public virtual IReactSiteConfiguration Configuration
+ {
+ get { return _config; }
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/ReactIdGenerator.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/ReactIdGenerator.cs
new file mode 100644
index 00000000..1f5efbe2
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/ReactIdGenerator.cs
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React
+{
+ ///
+ /// React ID generator.
+ ///
+ public class ReactIdGenerator : IReactIdGenerator
+ {
+ private static readonly string _encode32Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
+
+ private static long _random = DateTime.UtcNow.Ticks;
+
+ private static readonly char[] reactPrefix = "react_".ToCharArray();
+
+ ///
+ /// "react_".Length = 6 + 13 random symbols
+ ///
+ private const int reactIdLength = 19;
+
+ [ThreadStatic]
+ private static char[] _chars;
+
+ ///
+ /// Returns a short react identifier starts with "react_".
+ ///
+ ///
+ public string Generate()
+ {
+ var chars = _chars;
+ if (chars == null)
+ {
+ _chars = chars = new char[reactIdLength];
+ Array.Copy(reactPrefix, 0, chars, 0, reactPrefix.Length);
+ }
+
+ var id = Interlocked.Increment(ref _random);
+
+ // from 6 because "react_".Length == 6, _encode32Chars.Length == 32 (base32),
+ // base32 characters are 5 bits in length and from long (64 bits) we can get 13 symbols
+ chars[6] = _encode32Chars[(int)(id >> 60) & 31];
+ chars[7] = _encode32Chars[(int)(id >> 55) & 31];
+ chars[8] = _encode32Chars[(int)(id >> 50) & 31];
+ chars[9] = _encode32Chars[(int)(id >> 45) & 31];
+ chars[10] = _encode32Chars[(int)(id >> 40) & 31];
+ chars[11] = _encode32Chars[(int)(id >> 35) & 31];
+ chars[12] = _encode32Chars[(int)(id >> 30) & 31];
+ chars[13] = _encode32Chars[(int)(id >> 25) & 31];
+ chars[14] = _encode32Chars[(int)(id >> 20) & 31];
+ chars[15] = _encode32Chars[(int)(id >> 15) & 31];
+ chars[16] = _encode32Chars[(int)(id >> 10) & 31];
+ chars[17] = _encode32Chars[(int)(id >> 5) & 31];
+ chars[18] = _encode32Chars[(int)id & 31];
+
+ return new string(chars, 0, reactIdLength);
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/ReactSiteConfiguration.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/ReactSiteConfiguration.cs
new file mode 100644
index 00000000..e49df0e9
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/ReactSiteConfiguration.cs
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+using Microsoft.Extensions.DependencyInjection;
+using Newtonsoft.Json;
+using React.Exceptions;
+
+namespace React
+{
+ ///
+ /// Site-wide configuration for ReactJS.NET
+ ///
+ public class ReactSiteConfiguration : IReactSiteConfiguration
+ {
+ private readonly IServiceProvider _serviceProvider;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ReactSiteConfiguration(IServiceProvider serviceProvider)
+ {
+ _serviceProvider = serviceProvider;
+
+ ReuseJavaScriptEngines = true;
+ AllowJavaScriptPrecompilation = false;
+ LoadReact = true;
+ JsonSerializerSettings = new JsonSerializerSettings
+ {
+ StringEscapeHandling = StringEscapeHandling.EscapeHtml
+ };
+ UseDebugReact = false;
+ UseServerSideRendering = true;
+ ExceptionHandler = (Exception ex, string ComponentName, string ContainerId) =>
+ throw new ReactServerRenderingException(string.Format(
+ "Error while rendering \"{0}\" to \"{2}\": {1}",
+ ComponentName,
+ ex.Message,
+ ContainerId
+ ), ex);
+ }
+
+ ///
+ /// All the scripts that have been added to this configuration and require JSX
+ /// transformation to be run.
+ ///
+ private readonly IList _scriptFiles = new List();
+ ///
+ /// All the scripts that have been added to this configuration and do not require JSX
+ /// transformation to be run.
+ ///
+ private readonly IList _scriptFilesWithoutTransform = new List();
+
+ ///
+ /// Adds a script to the list of scripts that are executed. This should be called for all
+ /// React components and their dependencies. If the script does not have any JSX in it
+ /// (for example, it's built using Webpack or Gulp), use
+ /// instead.
+ ///
+ ///
+ /// Name of the file to execute. Should be a server relative path starting with ~ (eg.
+ /// ~/Scripts/Awesome.js)
+ ///
+ /// This configuration, for chaining
+ public IReactSiteConfiguration AddScript(string filename)
+ {
+ _scriptFiles.Add(filename);
+ return this;
+ }
+
+ ///
+ /// Adds a script to the list of scripts that are executed. This is the same as
+ /// except it does not run JSX transformation on the script and thus is
+ /// more efficient.
+ ///
+ ///
+ /// Name of the file to execute. Should be a server relative path starting with ~ (eg.
+ /// ~/Scripts/Awesome.js)
+ ///
+ /// The configuration, for chaining
+ public IReactSiteConfiguration AddScriptWithoutTransform(string filename)
+ {
+ _scriptFilesWithoutTransform.Add(filename);
+ return this;
+ }
+
+ ///
+ /// Gets all the file paths that match the specified pattern. If the pattern is a plain
+ /// path, just returns that path verbatim.
+ ///
+ ///
+ /// Patterns to search for (eg. ~/Scripts/*.js or ~/Scripts/Awesome.js
+ ///
+ /// File paths that match this pattern
+ private IEnumerable Glob(string glob)
+ {
+ if (!glob.IsGlobPattern())
+ {
+ return new[] { glob };
+ }
+ // Directly touching the IoC container is not ideal, but we only want to pull the FileSystem
+ // dependency if it's absolutely necessary.
+ var fileSystem = _serviceProvider.GetRequiredService();
+ return fileSystem.Glob(glob);
+ }
+
+ ///
+ /// Gets a list of all the scripts that have been added to this configuration and require JSX
+ /// transformation to be run.
+ ///
+ public IEnumerable Scripts
+ {
+ // TODO: It's a bit strange to do the globbing here, ideally this class should just be a simple
+ // bag of settings with no logic.
+ get { return _scriptFiles.SelectMany(Glob); }
+ }
+
+ ///
+ /// Gets a list of all the scripts that have been added to this configuration.
+ ///
+ public IEnumerable ScriptsWithoutTransform
+ {
+ get { return _scriptFilesWithoutTransform.SelectMany(Glob); }
+ }
+
+ ///
+ /// Gets or sets the configuration for JSON serializer.
+ ///
+ public JsonSerializerSettings JsonSerializerSettings { get; set; }
+
+ ///
+ /// Sets the configuration for json serializer.
+ ///
+ /// Settings.
+ ///
+ /// Thic confiquration is used when component initialization script
+ /// is being generated server-side.
+ ///
+ public IReactSiteConfiguration SetJsonSerializerSettings(JsonSerializerSettings settings)
+ {
+ JsonSerializerSettings = settings;
+ return this;
+ }
+
+ ///
+ /// Gets or sets whether JavaScript engines should be reused across requests.
+ ///
+ public bool ReuseJavaScriptEngines { get; set; }
+ ///
+ /// Sets whether JavaScript engines should be reused across requests.
+ ///
+ public IReactSiteConfiguration SetReuseJavaScriptEngines(bool value)
+ {
+ ReuseJavaScriptEngines = value;
+ return this;
+ }
+
+ ///
+ /// Gets or sets the number of engines to initially start when a pool is created.
+ /// Defaults to 10.
+ ///
+ public int? StartEngines { get; set; }
+ ///
+ /// Sets the number of engines to initially start when a pool is created.
+ /// Defaults to 10.
+ ///
+ public IReactSiteConfiguration SetStartEngines(int? startEngines)
+ {
+ StartEngines = startEngines;
+ return this;
+ }
+
+ ///
+ /// Gets or sets the maximum number of engines that will be created in the pool.
+ /// Defaults to 25.
+ ///
+ public int? MaxEngines { get; set; }
+ ///
+ /// Sets the maximum number of engines that will be created in the pool.
+ /// Defaults to 25.
+ ///
+ public IReactSiteConfiguration SetMaxEngines(int? maxEngines)
+ {
+ MaxEngines = maxEngines;
+ return this;
+ }
+
+ ///
+ /// Gets or sets the maximum number of times an engine can be reused before it is disposed.
+ /// 0 is unlimited. Defaults to 100.
+ ///
+ public int? MaxUsagesPerEngine { get; set; }
+ ///
+ /// Sets the maximum number of times an engine can be reused before it is disposed.
+ /// 0 is unlimited. Defaults to 100.
+ ///
+ public IReactSiteConfiguration SetMaxUsagesPerEngine(int? maxUsagesPerEngine)
+ {
+ MaxUsagesPerEngine = maxUsagesPerEngine;
+ return this;
+ }
+
+ ///
+ /// Gets or sets whether to allow the JavaScript pre-compilation (accelerates the
+ /// initialization of JavaScript engines).
+ ///
+ public bool AllowJavaScriptPrecompilation { get; set; }
+
+ ///
+ /// Sets whether to allow the JavaScript pre-compilation (accelerates the initialization of
+ /// JavaScript engines).
+ ///
+ ///
+ public IReactSiteConfiguration SetAllowJavaScriptPrecompilation(bool allowJavaScriptPrecompilation)
+ {
+ AllowJavaScriptPrecompilation = allowJavaScriptPrecompilation;
+ return this;
+ }
+
+ ///
+ /// Gets or sets whether the built-in version of React is loaded. If false, you must
+ /// provide your own version of React.
+ ///
+ public bool LoadReact { get; set; }
+
+ ///
+ /// Sets whether the built-in version of React is loaded. If false, you must
+ /// provide your own version of React.
+ ///
+ /// The configuration, for chaining
+ public IReactSiteConfiguration SetLoadReact(bool loadReact)
+ {
+ LoadReact = loadReact;
+ return this;
+ }
+
+ ///
+ /// Gets or sets whether to use the debug version of React. This is slower, but gives
+ /// useful debugging tips.
+ ///
+ public bool UseDebugReact { get; set; }
+
+ ///
+ /// Sets whether to use the debug version of React. This is slower, but gives
+ /// useful debugging tips.
+ ///
+ public IReactSiteConfiguration SetUseDebugReact(bool value)
+ {
+ UseDebugReact = value;
+ return this;
+ }
+
+ ///
+ /// Gets or sets whether server-side rendering is enabled.
+ ///
+ public bool UseServerSideRendering { get; set; }
+
+ ///
+ /// Disables server-side rendering. This is useful when debugging your scripts.
+ ///
+ public IReactSiteConfiguration DisableServerSideRendering()
+ {
+ UseServerSideRendering = false;
+ return this;
+ }
+
+ ///
+ /// Handle an exception caught during server-render of a component.
+ /// If unset, unhandled exceptions will be thrown for all component renders.
+ ///
+ public Action ExceptionHandler { get; set; }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public IReactSiteConfiguration SetExceptionHandler(Action handler)
+ {
+ ExceptionHandler = handler;
+ return this;
+ }
+
+ ///
+ /// A provider that returns a nonce to be used on any script tags on the page.
+ /// This value must match the nonce used in the Content Security Policy header on the response.
+ ///
+ public Func ScriptNonceProvider { get; set; }
+
+ ///
+ /// Sets a provider that returns a nonce to be used on any script tags on the page.
+ /// This value must match the nonce used in the Content Security Policy header on the response.
+ ///
+ ///
+ ///
+ public IReactSiteConfiguration SetScriptNonceProvider(Func provider)
+ {
+ ScriptNonceProvider = provider;
+ return this;
+ }
+
+ ///
+ /// The path to the application bundles built by webpack or create-react-app
+ ///
+ public string ReactAppBuildPath { get; set; }
+
+ ///
+ /// Sets the path to the application bundles built by webpack or create-react-app
+ ///
+ ///
+ ///
+ public IReactSiteConfiguration SetReactAppBuildPath(string reactAppBuildPath)
+ {
+ ReactAppBuildPath = reactAppBuildPath;
+ return this;
+ }
+
+ ///
+ /// Gets or sets if the React 18+ create root api should be used for rendering / hydration.
+ /// If false ReactDOM.render / ReactDOM.hydrate will be used.
+ ///
+ public bool UseRootAPI { get; set; }
+
+ ///
+ /// Enables usage of the React 18 root API when rendering / hydrating.
+ ///
+ ///
+ public void EnableReact18RootAPI()
+ {
+ UseRootAPI = true;
+ }
+
+ ///
+ public Func FilterResource { get; set; }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/Redpoint.ThirdParty.React.Core.csproj b/UET/Lib/Redpoint.ThirdParty.React.Core/Redpoint.ThirdParty.React.Core.csproj
new file mode 100644
index 00000000..d8122af8
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/Redpoint.ThirdParty.React.Core.csproj
@@ -0,0 +1,44 @@
+
+
+
+
+
+ React.Core
+ React.Core
+
+
+
+
+ A fork of React.NET (https://github.com/reactjs/react.net) that adds support for React v18.
+ Redpoint.ThirdParty.React.Core
+ react, react.net
+ MIT
+ June Rhodes, Daniel Lo Nigro
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/ChainedRenderFunctions.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/ChainedRenderFunctions.cs
new file mode 100644
index 00000000..cdfa80ca
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/ChainedRenderFunctions.cs
@@ -0,0 +1,91 @@
+using System.Collections.ObjectModel;
+
+namespace React.RenderFunctions
+{
+ ///
+ /// Helper to chain functions to be executed during server-side rendering.
+ /// For instance, React Router and React Helmet can both be used together using this class.
+ ///
+ public class ChainedRenderFunctions : IRenderFunctions
+ {
+ private readonly ReadOnlyCollection _chainedFunctions;
+
+ ///
+ /// Constructor. Supports chained calls to multiple render functions by passing in a set of functions that should be called next.
+ ///
+ /// The chained render functions to call
+ public ChainedRenderFunctions(params IRenderFunctions[] chainedFunctions)
+ {
+ _chainedFunctions = chainedFunctions.Where(x => x != null).ToList().AsReadOnly();
+ }
+
+ ///
+ /// Executes before component render.
+ /// It takes a func that accepts a Javascript code expression to evaluate, which returns the result of the expression.
+ /// This is useful for setting up variables that will be referenced after the render completes.
+ /// The func to execute
+ ///
+ public void PreRender(Func executeJs)
+ {
+ foreach (var chainedFunction in _chainedFunctions)
+ {
+ chainedFunction.PreRender(executeJs);
+ }
+ }
+
+
+ ///
+ /// Transforms the React.createElement expression.
+ /// This is useful for libraries like styled components which require wrapping the root component
+ /// inside a helper to generate a stylesheet.
+ /// Example transform: React.createElement(Foo, ...) => wrapComponent(React.createElement(Foo, ...))
+ ///
+ /// The Javascript expression to wrap
+ /// A wrapped expression
+ public string WrapComponent(string componentToRender)
+ {
+ string wrappedComponent = componentToRender;
+
+ foreach (var chainedFunction in _chainedFunctions)
+ {
+ wrappedComponent = chainedFunction.WrapComponent(wrappedComponent);
+ }
+
+ return wrappedComponent;
+ }
+
+
+ ///
+ /// Transforms the compiled rendered component HTML
+ /// This is useful for libraries like emotion which take rendered component HTML and output the transformed HTML plus additional style tags
+ ///
+ /// The component HTML
+ /// A wrapped expression
+ public string TransformRenderedHtml(string input)
+ {
+ string renderedHtml = input;
+
+ foreach (var chainedFunction in _chainedFunctions)
+ {
+ renderedHtml = chainedFunction.TransformRenderedHtml(renderedHtml);
+ }
+
+ return renderedHtml;
+ }
+
+
+ ///
+ /// Executes after component render.
+ /// It takes a func that accepts a Javascript code expression to evaluate, which returns the result of the expression.
+ /// This is useful for reading computed state, such as generated stylesheets or a router redirect result.
+ ///
+ /// The func to execute
+ public void PostRender(Func executeJs)
+ {
+ foreach (var chainedFunction in _chainedFunctions)
+ {
+ chainedFunction.PostRender(executeJs);
+ }
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/EmotionFunctions.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/EmotionFunctions.cs
new file mode 100644
index 00000000..7971bc68
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/EmotionFunctions.cs
@@ -0,0 +1,19 @@
+namespace React.RenderFunctions
+{
+ ///
+ /// Render functions for Emotion. https://github.com/emotion-js/emotion
+ /// Requires `emotion-server` to be exposed globally as `EmotionServer`
+ ///
+ public class EmotionFunctions : RenderFunctionsBase
+ {
+ ///
+ /// Implementation of TransformRenderedHtml
+ ///
+ ///
+ ///
+ public override string TransformRenderedHtml(string input)
+ {
+ return $"EmotionServer.renderStylesToString({input})";
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/ReactHelmetFunctions.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/ReactHelmetFunctions.cs
new file mode 100644
index 00000000..0caee24d
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/ReactHelmetFunctions.cs
@@ -0,0 +1,31 @@
+using Newtonsoft.Json;
+
+namespace React.RenderFunctions
+{
+ ///
+ /// Render functions for React-Helmet. https://github.com/nfl/react-helmet
+ /// Requires `react-helmet` to be exposed globally as `Helmet`
+ ///
+ public class ReactHelmetFunctions : RenderFunctionsBase
+ {
+ ///
+ /// Dictionary of Helmet properties, rendered as raw HTML tags
+ /// Available keys: "base", "bodyAttributes", "htmlAttributes", "link", "meta", "noscript", "script", "style", "title"
+ ///
+ public Dictionary RenderedHelmet { get; private set; }
+
+ ///
+ /// Implementation of PostRender
+ ///
+ ///
+ public override void PostRender(Func executeJs)
+ {
+ var helmetString = executeJs(@"
+var helmetResult = Helmet.renderStatic();
+JSON.stringify(['base', 'bodyAttributes', 'htmlAttributes', 'link', 'meta', 'noscript', 'script', 'style', 'title']
+ .reduce((mappedResults, helmetKey) => Object.assign(mappedResults, { [helmetKey]: helmetResult[helmetKey] && helmetResult[helmetKey].toString() }), {}));");
+
+ RenderedHelmet = JsonConvert.DeserializeObject>(helmetString);
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/ReactJssFunctions.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/ReactJssFunctions.cs
new file mode 100644
index 00000000..18ca4bd8
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/ReactJssFunctions.cs
@@ -0,0 +1,42 @@
+namespace React.RenderFunctions
+{
+ ///
+ /// Render functions for React-JSS. https://github.com/cssinjs/react-jss
+ /// Requires `react-jss` to be exposed globally as `ReactJss`
+ ///
+ public class ReactJssFunctions : RenderFunctionsBase
+ {
+ ///
+ /// HTML style tag containing the rendered styles
+ ///
+ public string RenderedStyles { get; private set; }
+
+ ///
+ /// Implementation of PreRender
+ ///
+ ///
+ public override void PreRender(Func executeJs)
+ {
+ executeJs("var reactJssProps = { registry: new ReactJss.SheetsRegistry() };");
+ }
+
+ ///
+ /// Implementation of WrapComponent
+ ///
+ ///
+ ///
+ public override string WrapComponent(string componentToRender)
+ {
+ return ($"React.createElement(ReactJss.JssProvider, reactJssProps, ({componentToRender}))");
+ }
+
+ ///
+ /// Implementation of PostRender
+ ///
+ ///
+ public override void PostRender(Func executeJs)
+ {
+ RenderedStyles = $"";
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/StyledComponentsFunctions.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/StyledComponentsFunctions.cs
new file mode 100644
index 00000000..863d0db2
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctions/StyledComponentsFunctions.cs
@@ -0,0 +1,42 @@
+namespace React.RenderFunctions
+{
+ ///
+ /// Render functions for styled components. https://github.com/styled-components/styled-components
+ /// Requires `styled-components` to be exposed globally as `Styled`
+ ///
+ public class StyledComponentsFunctions : RenderFunctionsBase
+ {
+ ///
+ /// HTML style tag containing the rendered styles
+ ///
+ public string RenderedStyles { get; private set; }
+
+ ///
+ /// Implementation of PreRender
+ ///
+ ///
+ public override void PreRender(Func executeJs)
+ {
+ executeJs("var serverStyleSheet = new Styled.ServerStyleSheet();");
+ }
+
+ ///
+ /// Implementation of WrapComponent
+ ///
+ ///
+ ///
+ public override string WrapComponent(string componentToRender)
+ {
+ return ($"serverStyleSheet.collectStyles({componentToRender})");
+ }
+
+ ///
+ /// Implementation of PostRender
+ ///
+ ///
+ public override void PostRender(Func executeJs)
+ {
+ RenderedStyles = executeJs("serverStyleSheet.getStyleTags()");
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctionsBase.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctionsBase.cs
new file mode 100644
index 00000000..633dd035
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/RenderFunctionsBase.cs
@@ -0,0 +1,50 @@
+namespace React
+{
+ ///
+ /// Functions to execute during a render request.
+ /// These functions will share the same Javascript context, so state can be passed around via variables.
+ ///
+ public abstract class RenderFunctionsBase : IRenderFunctions
+ {
+ ///
+ /// Executes before component render.
+ /// It takes a func that accepts a Javascript code expression to evaluate, which returns the result of the expression.
+ /// This is useful for setting up variables that will be referenced after the render completes.
+ /// The func to execute
+ ///
+ public virtual void PreRender(Func executeJs)
+ {
+ }
+
+
+ ///
+ /// Transforms the React.createElement expression.
+ /// This is useful for libraries like styled components which require wrapping the root component
+ /// inside a helper to generate a stylesheet.
+ /// Example transform: React.createElement(Foo, ...) => wrapComponent(React.createElement(Foo, ...))
+ ///
+ /// The Javascript expression to wrap
+ /// A wrapped expression
+ public virtual string WrapComponent(string componentToRender) => componentToRender;
+
+
+ ///
+ /// Transforms the compiled rendered component HTML
+ /// This is useful for libraries like emotion which take rendered component HTML and output the transformed HTML plus additional style tags
+ ///
+ /// The component HTML
+ /// A wrapped expression
+ public virtual string TransformRenderedHtml(string input) => input;
+
+
+ ///
+ /// Executes after component render.
+ /// It takes a func that accepts a Javascript code expression to evaluate, which returns the result of the expression.
+ /// This is useful for reading computed state, such as generated stylesheets or a router redirect result.
+ ///
+ /// The func to execute
+ public virtual void PostRender(Func executeJs)
+ {
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/Resources/shims.js b/UET/Lib/Redpoint.ThirdParty.React.Core/Resources/shims.js
new file mode 100644
index 00000000..f99e4f26
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/Resources/shims.js
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+var global = global || {};
+var React, ReactDOM, ReactDOMServer, setTimeout, clearTimeout;
+
+// Basic console shim. Caches all calls to console methods.
+function MockConsole() {
+ this._calls = [];
+ ['log', 'error', 'warn', 'debug', 'info', 'dir', 'group', 'groupEnd', 'groupCollapsed'].forEach(function (methodName) {
+ this[methodName] = this._handleCall.bind(this, methodName);
+ }, this);
+}
+MockConsole.prototype = {
+ _handleCall: function(methodName/*, ...args*/) {
+ var serializedArgs = [];
+ for (var i = 1; i < arguments.length; i++) {
+ serializedArgs.push(JSON.stringify(arguments[i]));
+ }
+
+ this._calls.push({
+ method: methodName,
+ args: serializedArgs,
+ stack: '\nCall stack: ' + (new Error().stack || 'not available')
+ });
+ },
+ _formatCall: function(call) {
+ return 'console.' + call.method + '("[.NET]", ' + call.args.join(', ') + ', ' + JSON.stringify(call.stack) + ');';
+ },
+ getCalls: function() {
+ return this._calls.map(this._formatCall).join('\n');
+ }
+};
+var console = new MockConsole();
+
+if (!Object.freeze) {
+ Object.freeze = function() { };
+}
+
+/**
+ * Finds a user-supplied version of React and ensures it's exposed globally.
+ *
+ * @return {string} Comma-separated list of missing globals.
+ */
+function ReactNET_initReact() {
+ var missing = [];
+
+ if (typeof React === 'undefined') {
+ if (global.React) {
+ React = global.React;
+ } else {
+ missing.push('React');
+ }
+ }
+
+ if (typeof ReactDOM === 'undefined') {
+ if (global.ReactDOM) {
+ ReactDOM = global.ReactDOM;
+ } else {
+ missing.push('ReactDOM');
+ }
+ }
+
+ if (typeof ReactDOMServer === 'undefined') {
+ if (global.ReactDOMServer) {
+ ReactDOMServer = global.ReactDOMServer;
+ }
+ else {
+ missing.push('ReactDOMServer');
+ }
+ }
+
+ return missing.join(',');
+}
+
+setTimeout = setTimeout || global.setTimeout;
+if (setTimeout === undefined) {
+ setTimeout = function() {
+ throw new Error('setTimeout is not supported in server-rendered Javascript.');
+ }
+}
+
+clearTimeout = clearTimeout || global.clearTimeout;
+if (clearTimeout === undefined) {
+ clearTimeout = function() {
+ throw new Error('clearTimeout is not supported in server-rendered Javascript.');
+ }
+}
+
+/**
+ * Polyfill for engines that do not support Object.assign
+ */
+if (typeof Object.assign !== 'function') {
+ Object.assign = function (target, varArgs) { // .length of function is 2
+ 'use strict';
+ if (target == null) { // TypeError if undefined or null
+ throw new TypeError('Cannot convert undefined or null to object');
+ }
+
+ var to = Object(target);
+
+ for (var index = 1; index < arguments.length; index++) {
+ var nextSource = arguments[index];
+
+ if (nextSource != null) { // Skip over if undefined or null
+ for (var nextKey in nextSource) {
+ // Avoid bugs when hasOwnProperty is shadowed
+ if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
+ to[nextKey] = nextSource[nextKey];
+ }
+ }
+ }
+ }
+ return to;
+ };
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/SimpleFileSystem.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/SimpleFileSystem.cs
new file mode 100644
index 00000000..acfcad83
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/SimpleFileSystem.cs
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+namespace React
+{
+ ///
+ /// An implementation of that does not do any mapping of file paths.
+ ///
+ public class SimpleFileSystem : FileSystemBase
+ {
+ ///
+ /// Converts a path from an application relative path (~/...) to a full filesystem path
+ ///
+ /// App-relative path of the file
+ /// Full path of the file
+ public override string MapPath(string relativePath)
+ {
+ return relativePath;
+ }
+ }
+}
diff --git a/UET/Lib/Redpoint.ThirdParty.React.Core/SourceMap.cs b/UET/Lib/Redpoint.ThirdParty.React.Core/SourceMap.cs
new file mode 100644
index 00000000..e5efbd94
--- /dev/null
+++ b/UET/Lib/Redpoint.ThirdParty.React.Core/SourceMap.cs
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace React
+{
+ ///
+ /// Represents the data contained in a source map
+ ///
+ [Serializable]
+ public class SourceMap
+ {
+ ///
+ /// Version number of the source map spec used to build this source map. Expected
+ /// to be version 3.
+ ///
+ public int Version { get; set; }
+
+ ///
+ /// An optional name of the generated code that this source map is associated with.
+ ///
+ public string File { get; set; }
+
+ ///
+ /// An optional source root, useful for relocating source files on a server or
+ /// removing repeated values in the entry. This value is
+ /// prepended to the individual entries in the field.
+ ///
+ public string SourceRoot { get; set; }
+
+ ///
+ /// A list of original sources used by the entry.
+ ///
+ public IList Sources { get; set; }
+
+ ///
+ /// An optional list of source content, useful when the can't
+ /// be hosted. The contents are listed in the same order as the .
+ /// null may be used if some original sources should be retrieved by name.
+ ///
+ public IList SourcesContent { get; set; }
+
+ ///
+ /// A list of symbol names used by the entry.
+ ///
+ public IList Names { get; set; }
+
+ ///
+ /// A string with the mapping data encoded in base 64 VLQ.
+ ///
+ public string Mappings { get; set; }
+
+ ///
+ /// Outputs this source map as JSON.
+ ///
+ ///
+ public string ToJson()
+ {
+ return JsonConvert.SerializeObject(this, new JsonSerializerSettings
+ {
+ // Camelcase keys (eg. "SourcesContent" -> "sourcesContent")
+ ContractResolver = new CamelCasePropertyNamesContractResolver()
+ });
+ }
+
+ ///
+ /// Parse a source map from JSON
+ ///
+ /// JSON input
+ /// Source map
+ public static SourceMap FromJson(string json)
+ {
+ return JsonConvert.DeserializeObject(json);
+ }
+ }
+}
diff --git a/UET/Lib/ThirdPartyCommon.Build.props b/UET/Lib/ThirdPartyCommon.Build.props
new file mode 100644
index 00000000..4fe26f87
--- /dev/null
+++ b/UET/Lib/ThirdPartyCommon.Build.props
@@ -0,0 +1,15 @@
+
+
+
+ true
+ true
+
+
+
+
+
+ enable
+ annotations
+
+
+
diff --git a/UET/Lib/XunitTesting.Build.props b/UET/Lib/XunitTesting.Build.props
index f9db5820..b4b333d9 100644
--- a/UET/Lib/XunitTesting.Build.props
+++ b/UET/Lib/XunitTesting.Build.props
@@ -14,27 +14,7 @@
true
-
-
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
-
-
-
+
diff --git a/UET/Lib/XunitTestingDependencies.Build.props b/UET/Lib/XunitTestingDependencies.Build.props
new file mode 100644
index 00000000..cfc33012
--- /dev/null
+++ b/UET/Lib/XunitTestingDependencies.Build.props
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/UET/Redpoint.CloudFramework.CLI/BuildClientApp.cs b/UET/Redpoint.CloudFramework.CLI/BuildClientApp.cs
new file mode 100644
index 00000000..723efd33
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.CLI/BuildClientApp.cs
@@ -0,0 +1,130 @@
+namespace Redpoint.CloudFramework.CLI
+{
+ using Microsoft.Extensions.Logging;
+ using Redpoint.CommandLine;
+ using System.CommandLine;
+ using System.Threading.Tasks;
+ using Redpoint.ProcessExecution;
+
+ internal class BuildClientApp
+ {
+ internal class Options
+ {
+ public Option Path = new Option(
+ "--app-path",
+ "The path to the client application. This directory should have a package.json file in it.");
+
+ public Option Configuration = new Option(
+ "--configuration",
+ "The build configuration; should be set to $(Configuration) from MSBuild.");
+ }
+
+ public static Command CreateCommand(ICommandBuilder builder)
+ {
+ return new Command("build-client-app", "Builds a TypeScript-based client app, installing all required dependencies as needed.");
+ }
+
+ internal class CommandInstance : ICommandInstance
+ {
+ private readonly ILogger _logger;
+ private readonly IYarnInstallationService _yarnInstallationService;
+ private readonly IProcessExecutor _processExecutor;
+ private readonly Options _options;
+
+ private static readonly string[] _openapiGenerateArgs = new[] { "run", "openapi", "--input", "openapi.json", "--output", "src/api", "-c", "fetch" };
+ private static readonly string[] _webpackProductionBuildArgs = new[] { "run", "webpack", "--progress", "--mode", "production" };
+ private static readonly string[] _yarnInstallArgs = new[] { "install", "--json" };
+
+ public CommandInstance(
+ ILogger logger,
+ IYarnInstallationService yarnInstallationService,
+ IProcessExecutor processExecutor,
+ Options options)
+ {
+ _logger = logger;
+ _yarnInstallationService = yarnInstallationService;
+ _processExecutor = processExecutor;
+ _options = options;
+ }
+
+ public async Task ExecuteAsync(ICommandInvocationContext context)
+ {
+ var appPath = context.ParseResult.GetValueForOption(_options.Path);
+ if (appPath == null || !appPath.Exists || !File.Exists(Path.Combine(appPath.FullName, "package.json")))
+ {
+ _logger.LogError("Expected --app-path to exist and contain a package.json file.");
+ return 1;
+ }
+ var configuration = context.ParseResult.GetValueForOption(_options.Configuration) ?? string.Empty;
+
+ // Install Yarn.
+ var (exitCode, yarnCorepackShimPath) = await _yarnInstallationService.InstallYarnIfNeededAsync(context.GetCancellationToken()).ConfigureAwait(true);
+ if (yarnCorepackShimPath == null)
+ {
+ return exitCode;
+ }
+
+ // Run 'yarn install' to install dependencies.
+ exitCode = await _processExecutor.ExecuteAsync(
+ new ProcessSpecification
+ {
+ FilePath = yarnCorepackShimPath,
+ Arguments = _yarnInstallArgs.Select(x => new LogicalProcessArgument(x)),
+ WorkingDirectory = appPath.FullName,
+ },
+ new YarnInstallCaptureSpecification(_logger),
+ context.GetCancellationToken()).ConfigureAwait(true);
+ if (exitCode != 0)
+ {
+ _logger.LogError("'yarn install' command failed; see above for output.");
+ return exitCode;
+ }
+
+ // If an openapi.json file exists in the root of the application path,
+ // automatically generate the TypeScript API for it.
+ var openapiPath = Path.Combine(appPath.FullName, "openapi.json");
+ if (File.Exists(openapiPath))
+ {
+ exitCode = await _processExecutor.ExecuteAsync(
+ new ProcessSpecification
+ {
+ FilePath = yarnCorepackShimPath,
+ Arguments = _openapiGenerateArgs.Select(x => new LogicalProcessArgument(x)),
+ WorkingDirectory = appPath.FullName,
+ },
+ CaptureSpecification.Passthrough,
+ context.GetCancellationToken()).ConfigureAwait(true);
+ if (exitCode != 0)
+ {
+ _logger.LogError("'yarn run openapi' command failed; see above for output.");
+ return exitCode;
+ }
+ }
+
+ if (configuration == "Release")
+ {
+ // If we're building for Release, build the production version of the application.
+ return await _processExecutor.ExecuteAsync(
+ new ProcessSpecification
+ {
+ FilePath = yarnCorepackShimPath,
+ Arguments = _webpackProductionBuildArgs.Select(x => new LogicalProcessArgument(x)),
+ WorkingDirectory = appPath.FullName,
+ EnvironmentVariables = File.Exists(Path.Combine(appPath.FullName, "tsconfig.webpack.json")) ? new Dictionary
+ {
+ { "TS_NODE_PROJECT", "tsconfig.webpack.json" }
+ } : null
+ },
+ CaptureSpecification.Passthrough,
+ context.GetCancellationToken()).ConfigureAwait(true);
+ }
+ else
+ {
+ // If we're building for Debug, then Redpoint.CloudFramework will handle running
+ // Webpack in watch mode.
+ return 0;
+ }
+ }
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.CLI/DefaultYarnInstallationService.cs b/UET/Redpoint.CloudFramework.CLI/DefaultYarnInstallationService.cs
new file mode 100644
index 00000000..e2665718
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.CLI/DefaultYarnInstallationService.cs
@@ -0,0 +1,123 @@
+namespace Redpoint.CloudFramework.CLI
+{
+ using Microsoft.Extensions.Logging;
+ using Redpoint.PathResolution;
+ using Redpoint.ProcessExecution;
+ using System;
+ using System.Diagnostics;
+ using System.Threading.Tasks;
+
+ internal class DefaultYarnInstallationService : IYarnInstallationService
+ {
+ private readonly ILogger _logger;
+ private readonly IPathResolver _pathResolver;
+ private readonly IProcessExecutor _processExecutor;
+
+ private static readonly string[] _winGetInstallNodeJsArgs = new[] { "install", "OpenJS.NodeJS" };
+
+ public DefaultYarnInstallationService(
+ ILogger logger,
+ IPathResolver pathResolver,
+ IProcessExecutor processExecutor)
+ {
+ _logger = logger;
+ _pathResolver = pathResolver;
+ _processExecutor = processExecutor;
+ }
+
+ public async Task<(int exitCode, string? yarnPath)> InstallYarnIfNeededAsync(CancellationToken cancellationToken)
+ {
+ int exitCode;
+
+ // Check if we have Node.js installed. If we don't, try to use WinGet to install it.
+ var node = await _pathResolver.ResolveBinaryPath("node").ConfigureAwait(true);
+ var corepack = await _pathResolver.ResolveBinaryPath("corepack").ConfigureAwait(true);
+ if (node == null || corepack == null)
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ var winget = await _pathResolver.ResolveBinaryPath("winget").ConfigureAwait(true);
+ if (winget == null)
+ {
+ winget = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft", "WindowsApps", "winget.exe");
+ if (!File.Exists(winget))
+ {
+ winget = null;
+ }
+ }
+ if (winget == null)
+ {
+ if (node != null)
+ {
+ _logger.LogError("WinGet is not installed, so Node.js can't be upgraded automatically. Please install WinGet by installing App Installer from the Microsoft Store first.");
+ }
+ else
+ {
+ _logger.LogError("WinGet is not installed, so Node.js can't be installed automatically. Please install WinGet by installing App Installer from the Microsoft Store first.");
+ }
+ Process.Start("https://www.microsoft.com/p/app-installer/9nblggh4nns1#activetab=pivot:overviewtab");
+ return (1, null);
+ }
+
+ _logger.LogInformation("Installing Node.js via WinGet...");
+ exitCode = await _processExecutor.ExecuteAsync(
+ new ProcessSpecification
+ {
+ FilePath = winget,
+ Arguments = _winGetInstallNodeJsArgs.Select(x => new LogicalProcessArgument(x)),
+ },
+ CaptureSpecification.Passthrough,
+ cancellationToken).ConfigureAwait(true);
+ if (exitCode != 0)
+ {
+ _logger.LogError("'winget' command failed; see above for output.");
+ return (exitCode, null);
+ }
+
+ node = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "nodejs", "node.exe");
+ corepack = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "nodejs", "corepack.cmd");
+ if (!File.Exists(node))
+ {
+ _logger.LogError("Node.js did not install correctly from WinGet, or did not install into the usual place. If Node.js did install correctly, try logging out and logging back in to refresh your environment variables.");
+ return (1, null);
+ }
+ if (!File.Exists(corepack))
+ {
+ _logger.LogError("The version of Node.js that is installed on this machine is not new enough to have 'corepack'. Upgrade Node.js manually and then try again. If Node.js did upgrade correctly, try logging out and logging back in to refresh your environment variables.");
+ return (1, null);
+ }
+ }
+ else
+ {
+ _logger.LogError("Node.js is not installed on this machine, or is not new enough to have the 'corepack' command. Upgrade Node.js to at least v16.9.0 and try again.");
+ return (1, null);
+ }
+ }
+
+ // Create a temporary location to install the Corepack shims. We'll use this writable location instead of
+ // the default since on Windows you can't do 'corepack enable' without elevating to Administrator.
+ var corepackShimPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "rcf-corepack");
+ var yarnCorepackShimPath = Path.Combine(corepackShimPath, OperatingSystem.IsWindows() ? "yarn.cmd" : "yarn");
+ if (!File.Exists(yarnCorepackShimPath))
+ {
+ _logger.LogInformation("Setting up Node.js corepack shims...");
+ Directory.CreateDirectory(corepackShimPath);
+ exitCode = await _processExecutor.ExecuteAsync(
+ new ProcessSpecification
+ {
+ FilePath = corepack,
+ Arguments = new[] { "enable", "--install-directory", corepackShimPath }.Select(x => new LogicalProcessArgument(x)),
+ },
+ CaptureSpecification.Passthrough,
+ cancellationToken).ConfigureAwait(true);
+ if (exitCode != 0)
+ {
+ _logger.LogError("'corepack enable' command failed; see above for output.");
+ return (exitCode, null);
+ }
+ }
+
+ return (0, yarnCorepackShimPath);
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.CLI/GenerateHtmlFromMjml.cs b/UET/Redpoint.CloudFramework.CLI/GenerateHtmlFromMjml.cs
new file mode 100644
index 00000000..f2d7adef
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.CLI/GenerateHtmlFromMjml.cs
@@ -0,0 +1,154 @@
+namespace Redpoint.CloudFramework.CLI
+{
+ using Microsoft.Extensions.Logging;
+ using Redpoint.CommandLine;
+ using System.CommandLine;
+ using System.Threading.Tasks;
+ using Redpoint.ProcessExecution;
+ using System.Text;
+
+ internal class GenerateHtmlFromMjml
+ {
+ internal class Options
+ {
+ public Option Path = new Option("--path", "The path to the MJML file.");
+ }
+
+ public static Command CreateCommand(ICommandBuilder builder)
+ {
+ return new Command("generate-html-from-mjml", "Generates a HTML file from an MJML file.");
+ }
+
+ internal class CommandInstance : ICommandInstance
+ {
+ private readonly ILogger _logger;
+ private readonly IYarnInstallationService _yarnInstallationService;
+ private readonly IProcessExecutor _processExecutor;
+ private readonly Options _options;
+
+ internal static readonly string[] _yarnInitArgs = new[] { "init", "-2" };
+ internal static readonly string[] _yarnAddMjmlArgs = new[] { "add", "-D", "mjml" };
+ internal static readonly string[] _yarnAddHtmlToTextArgs = new[] { "add", "-D", "@html-to/text-cli", "dom-serializer" };
+ internal static readonly string[] _htmlToTextArgs = new[] { "run", "html-to-text", "--selectors[]", ":[0].selector=h1", ":[0].format=skip" };
+
+ public CommandInstance(
+ ILogger logger,
+ IYarnInstallationService yarnInstallationService,
+ IProcessExecutor processExecutor,
+ Options options)
+ {
+ _logger = logger;
+ _yarnInstallationService = yarnInstallationService;
+ _processExecutor = processExecutor;
+ _options = options;
+ }
+
+ public async Task ExecuteAsync(ICommandInvocationContext context)
+ {
+ var inputPath = context.ParseResult.GetValueForOption(_options.Path);
+ if (inputPath == null || !inputPath.Exists)
+ {
+ _logger.LogError("Expected --input-path to exist.");
+ return 1;
+ }
+
+ // Install Yarn.
+ var (exitCode, yarnCorepackShimPath) = await _yarnInstallationService.InstallYarnIfNeededAsync(context.GetCancellationToken()).ConfigureAwait(true);
+ if (yarnCorepackShimPath == null)
+ {
+ return exitCode;
+ }
+
+ // Create our directory where we will install the mjml tool.
+ var mjmlInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "rcf-mjml");
+ Directory.CreateDirectory(mjmlInstallPath);
+
+ // If Yarn isn't initialised in this directory, do it now.
+ if (!File.Exists(Path.Combine(mjmlInstallPath, "yarn.lock")))
+ {
+ exitCode = await _processExecutor.ExecuteAsync(
+ new ProcessSpecification
+ {
+ FilePath = yarnCorepackShimPath,
+ Arguments = _yarnInitArgs.Select(x => new LogicalProcessArgument(x)),
+ WorkingDirectory = mjmlInstallPath,
+ },
+ new YarnInstallCaptureSpecification(_logger),
+ context.GetCancellationToken()).ConfigureAwait(true);
+ if (exitCode != 0)
+ {
+ _logger.LogError("'yarn init -2' command failed; see above for output.");
+ return exitCode;
+ }
+ }
+
+ // If package.json doesn't have mjml, install it.
+ if (!File.ReadAllText(Path.Combine(mjmlInstallPath, "package.json")).Contains(@"""mjml""", StringComparison.Ordinal))
+ {
+ exitCode = await _processExecutor.ExecuteAsync(
+ new ProcessSpecification
+ {
+ FilePath = yarnCorepackShimPath,
+ Arguments = _yarnAddMjmlArgs.Select(x => new LogicalProcessArgument(x)),
+ WorkingDirectory = mjmlInstallPath,
+ },
+ new YarnInstallCaptureSpecification(_logger),
+ context.GetCancellationToken()).ConfigureAwait(true);
+ if (exitCode != 0)
+ {
+ _logger.LogError("'yarn add -D mjml' command failed; see above for output.");
+ return exitCode;
+ }
+ }
+
+ // If package.json doesn't have @html-to/text-cli, install it.
+ if (!File.ReadAllText(Path.Combine(mjmlInstallPath, "package.json")).Contains(@"""@html-to/text-cli""", StringComparison.Ordinal))
+ {
+ exitCode = await _processExecutor.ExecuteAsync(
+ new ProcessSpecification
+ {
+ FilePath = yarnCorepackShimPath,
+ Arguments = _yarnAddHtmlToTextArgs.Select(x => new LogicalProcessArgument(x)),
+ WorkingDirectory = mjmlInstallPath,
+ },
+ new YarnInstallCaptureSpecification(_logger),
+ context.GetCancellationToken()).ConfigureAwait(true);
+ if (exitCode != 0)
+ {
+ _logger.LogError("'yarn add -D @html-to/text-cli' command failed; see above for output.");
+ return exitCode;
+ }
+ }
+
+ // Execute mjml for the input and output paths.
+ exitCode = await _processExecutor.ExecuteAsync(
+ new ProcessSpecification
+ {
+ FilePath = yarnCorepackShimPath,
+ Arguments = new[] { "run", "mjml", "-r", inputPath.FullName, "-o", inputPath.FullName + ".html" }.Select(x => new LogicalProcessArgument(x)),
+ WorkingDirectory = mjmlInstallPath,
+ },
+ CaptureSpecification.Passthrough,
+ context.GetCancellationToken()).ConfigureAwait(true);
+ if (exitCode != 0)
+ {
+ return exitCode;
+ }
+
+ // Execute html-to-text on the output HTML, so that we can have a text version as well.
+ var textStringBuilder = new StringBuilder();
+ exitCode = await _processExecutor.ExecuteAsync(
+ new ProcessSpecification
+ {
+ FilePath = yarnCorepackShimPath,
+ Arguments = _htmlToTextArgs.Select(x => new LogicalProcessArgument(x)),
+ WorkingDirectory = mjmlInstallPath,
+ },
+ new HtmlToTextCaptureSpecification(File.ReadAllText(inputPath.FullName + ".html"), textStringBuilder),
+ context.GetCancellationToken()).ConfigureAwait(true);
+ File.WriteAllText(inputPath.FullName + ".txt", textStringBuilder.ToString());
+ return exitCode;
+ }
+ }
+ }
+}
diff --git a/UET/Redpoint.CloudFramework.CLI/GenerateOpenApiJson.cs b/UET/Redpoint.CloudFramework.CLI/GenerateOpenApiJson.cs
new file mode 100644
index 00000000..85878735
--- /dev/null
+++ b/UET/Redpoint.CloudFramework.CLI/GenerateOpenApiJson.cs
@@ -0,0 +1,198 @@
+namespace Redpoint.CloudFramework.CLI
+{
+ using Microsoft.AspNetCore.Hosting;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Logging;
+ using Microsoft.OpenApi.Writers;
+ using Redpoint.CloudFramework.Startup;
+ using Redpoint.CommandLine;
+ using Swashbuckle.AspNetCore.Swagger;
+ using System;
+ using System.CommandLine;
+ using System.Globalization;
+ using System.Reflection;
+ using System.Runtime.Loader;
+ using System.Threading.Tasks;
+
+ internal class GenerateOpenApiJson
+ {
+ internal class Options
+ {
+ public Option AssemblyPath = new Option(
+ "--assembly-path",
+ "The path to the built .NET assembly.");
+
+ public Option OutputPath = new Option(
+ "--output-path",
+ "The path to output the OpenAPI JSON file to.");
+
+ public Option EntrypointClass = new Option(
+ "--entrypoint-class",
+ "The class name for the entrypoint.");
+
+ public Option Version = new Option(
+ "--version",
+ () => "v1",
+ "The document version to generate for.");
+ }
+
+ public static Command CreateCommand(ICommandBuilder builder)
+ {
+ return new Command("generate-openapi-json", "Generates an OpenAPI JSON file from the .NET assembly.");
+ }
+
+ internal class CommandInstance : ICommandInstance
+ {
+ private readonly ILogger _logger;
+ private readonly Options _options;
+
+ public CommandInstance(
+ ILogger logger,
+ Options options)
+ {
+ _logger = logger;
+ _options = options;
+ }
+
+#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
+#pragma warning disable IL2075 // 'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations.
+ public async Task ExecuteAsync(ICommandInvocationContext context)
+ {
+ var assemblyPath = context.ParseResult.GetValueForOption(_options.AssemblyPath);
+ if (assemblyPath == null || !assemblyPath.Exists)
+ {
+ _logger.LogError("The input assembly for generating the OpenAPI JSON (--assembly-path) must exist.");
+ return 1;
+ }
+ var outputPath = context.ParseResult.GetValueForOption(_options.OutputPath);
+ if (outputPath == null)
+ {
+ _logger.LogError("The output path for generating the OpenAPI JSON (--output-path) must be specified.");
+ return 1;
+ }
+ var entrypointClassName = context.ParseResult.GetValueForOption(_options.EntrypointClass);
+ var version = context.ParseResult.GetValueForOption(_options.Version);
+
+ Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development");
+ Environment.SetEnvironmentVariable("CLOUDFRAMEWORK_IS_CLIENT_API_GENERATION", "true");
+
+ AppDomain.CurrentDomain.AssemblyResolve += (sender, ev) =>
+ {
+ var name = new AssemblyName(ev.Name);
+ var baseDir = Path.GetDirectoryName(assemblyPath.FullName);
+ var targetFile = Path.Combine(baseDir!, name.Name + ".dll");
+ if (File.Exists(targetFile))
+ {
+ return AssemblyLoadContext.Default.LoadFromAssemblyPath(targetFile);
+ }
+ if (!targetFile.EndsWith(".resources.dll", StringComparison.InvariantCultureIgnoreCase))
+ {
+ _logger.LogError($"Unable to find assembly at: {targetFile}");
+ }
+ return null;
+ };
+
+ var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath.FullName);
+ if (assembly == null)
+ {
+ _logger.LogError($"Unable to load the assembly located at '{assemblyPath.FullName}'.");
+ return 1;
+ }
+
+ IWebHost? app;
+ var providerType = assembly.GetExportedTypes()
+ .FirstOrDefault(x => typeof(IWebAppProvider).IsAssignableFrom(x));
+ if (providerType == null)
+ {
+ if (string.IsNullOrWhiteSpace(entrypointClassName))
+ {
+ _logger.LogError("The entrypoint class (--entrypoint-class) must be specified because there is no class that implements IWebAppProvider.");
+ return 1;
+ }
+
+ _logger.LogWarning("You should migrate to having either Program or another public class implement IWebAppProvider instead of relying on Program having a static public GetWebHostAsync method and --entrypoint-class.");
+ var legacyTask = (Task?)assembly
+ .GetType(entrypointClassName)
+ ?.GetMethod("GetWebHost", BindingFlags.Static | BindingFlags.Public)
+ ?.Invoke(null, Array.Empty