diff --git a/.github/workflows/dev-pr-build.yml b/.github/workflows/dev-pr-build.yml index 727a27b42..505e73619 100644 --- a/.github/workflows/dev-pr-build.yml +++ b/.github/workflows/dev-pr-build.yml @@ -9,7 +9,7 @@ on: push: branches: [ "develop" ] workflow_dispatch: - + concurrency: group: dev-pr-build cancel-in-progress: ${{ !contains(github.actor, '[bot]') }} @@ -29,7 +29,7 @@ jobs: test: runs-on: [ self-hosted, Windows, X64 ] - needs: [actor-check] + needs: [ actor-check ] timeout-minutes: 90 if: needs.actor-check.outputs.was-bot != 'true' steps: @@ -41,7 +41,7 @@ jobs: private-key: ${{ secrets.SUBMODULE_PRIVATE_KEY }} owner: ${{ github.repository_owner }} repositories: 'GeoBlazor' - + # Checkout the repository to the GitHub Actions runner - name: Checkout uses: actions/checkout@v4 @@ -49,7 +49,7 @@ jobs: token: ${{ steps.app-token.outputs.token }} repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} ref: ${{ github.event.pull_request.head.ref || github.ref }} - + - name: Run Tests shell: pwsh env: @@ -63,7 +63,7 @@ jobs: build: runs-on: ubuntu-latest - needs: [actor-check] + needs: [ actor-check ] if: needs.actor-check.outputs.was-bot != 'true' timeout-minutes: 30 steps: @@ -75,8 +75,8 @@ jobs: private-key: ${{ secrets.SUBMODULE_PRIVATE_KEY }} owner: ${{ github.repository_owner }} repositories: 'GeoBlazor' - - # Checkout the repository to the GitHub Actions runner + + # Checkout the repository to the GitHub Actions runner - name: Checkout uses: actions/checkout@v4 with: @@ -99,7 +99,7 @@ jobs: - name: Build GeoBlazor shell: pwsh run: | - ./GeoBlazorBuild.ps1 -xml -pkg -docs -c "Release" + dotnet ./build-tools/GeoBlazorBuild.dll -xml -pkg -docs -c "Release" # Copies the nuget package to the artifacts directory - name: Upload nuget artifact @@ -108,7 +108,7 @@ jobs: name: .core-nuget retention-days: 4 path: ./dymaptic.GeoBlazor.Core.*.nupkg - + # xmllint is a dependency of the copy steps below - name: Install xmllint shell: bash @@ -129,8 +129,8 @@ jobs: run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ steps.app-token.outputs.token }} - - # This step will commit the updated version number back to the develop branch + + # This step will commit the updated version number back to the develop branch - name: Add Changes to Git if: github.event_name == 'pull_request' run: | diff --git a/.github/workflows/main-release-build.yml b/.github/workflows/main-release-build.yml index ab4a040ff..26910c343 100644 --- a/.github/workflows/main-release-build.yml +++ b/.github/workflows/main-release-build.yml @@ -9,8 +9,8 @@ on: workflow_dispatch: jobs: - build: - runs-on: [self-hosted, Windows, X64] + build: + runs-on: [ self-hosted, Windows, X64 ] timeout-minutes: 90 outputs: token: ${{ steps.app-token.outputs.token }} @@ -34,7 +34,7 @@ jobs: token: ${{ steps.app-token.outputs.token }} repository: ${{ github.repository }} ref: ${{ github.ref }} - + - name: Run Tests shell: pwsh env: @@ -50,8 +50,8 @@ jobs: - name: Build GeoBlazor shell: pwsh run: | - ./GeoBlazorBuild.ps1 -xml -pkg -pub -c "Release" - + dotnet ./build-tools/GeoBlazorBuild.dll -xml -pkg -pub -c "Release" + # xmllint is a dependency of the copy steps below - name: Install xmllint shell: bash @@ -71,7 +71,7 @@ jobs: with: name: .core-nuget path: ./dymaptic.GeoBlazor.Core.*.nupkg - + # This step will copy the PR description to an environment variable - name: Copy PR Release Notes run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d6ab46c3c..6c4291b76 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,40 +7,40 @@ on: push: branches: [ "test" ] workflow_dispatch: - + concurrency: group: test cancel-in-progress: true jobs: test: - runs-on: [self-hosted, Windows, X64] + runs-on: [ self-hosted, Windows, X64 ] timeout-minutes: 90 steps: - - name: Generate Github App token - uses: actions/create-github-app-token@v2 - id: app-token - with: - app-id: ${{ secrets.SUBMODULE_APP_ID }} - private-key: ${{ secrets.SUBMODULE_PRIVATE_KEY }} - owner: ${{ github.repository_owner }} - repositories: 'GeoBlazor' - - # Checkout the repository to the GitHub Actions runner - - name: Checkout - uses: actions/checkout@v4 - with: - token: ${{ steps.app-token.outputs.token }} - repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - ref: ${{ github.event.pull_request.head.ref || github.ref }} - - - name: Run Tests - shell: pwsh - env: - USE_CONTAINER: true - ARCGIS_API_KEY: ${{ secrets.ARCGIS_TESTING_API_KEY }} - GEOBLAZOR_CORE_LICENSE_KEY: ${{ secrets.GEOBLAZOR_CORE_LICENSE_KEY }} - HTTP_PORT: 8082 - HTTPS_PORT: 9445 - run: | - dotnet test --project ./test/dymaptic.GeoBlazor.Core.Test.Automation/dymaptic.GeoBlazor.Core.Test.Automation.csproj -c Release --filter CORE_ \ No newline at end of file + - name: Generate Github App token + uses: actions/create-github-app-token@v2 + id: app-token + with: + app-id: ${{ secrets.SUBMODULE_APP_ID }} + private-key: ${{ secrets.SUBMODULE_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: 'GeoBlazor' + + # Checkout the repository to the GitHub Actions runner + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ steps.app-token.outputs.token }} + repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} + ref: ${{ github.event.pull_request.head.ref || github.ref }} + + - name: Run Tests + shell: pwsh + env: + USE_CONTAINER: true + ARCGIS_API_KEY: ${{ secrets.ARCGIS_TESTING_API_KEY }} + GEOBLAZOR_CORE_LICENSE_KEY: ${{ secrets.GEOBLAZOR_CORE_LICENSE_KEY }} + HTTP_PORT: 8082 + HTTPS_PORT: 9445 + run: | + dotnet test --project ./test/dymaptic.GeoBlazor.Core.Test.Automation/dymaptic.GeoBlazor.Core.Test.Automation.csproj -c Release --filter CORE_ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index a2b9a02dd..e971b0a6a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,9 +38,9 @@ GeoBlazor is a Blazor component library that brings ArcGIS Maps SDK for JavaScri ### Build ```bash # Clean build of the Core project -pwsh GeoBlazorBuild.ps1 +dotnet ./build-tools/GeoBlazorBuild.dll -# GeoBlazorBuild.ps1 includes lots of options, use -h to see options +-- _`GeoBlazorBuild` includes lots of options, use -h to see options_ # Build entire solution dotnet build src/dymaptic.GeoBlazor.Core.sln @@ -49,9 +49,9 @@ dotnet build src/dymaptic.GeoBlazor.Core.sln dotnet build src/dymaptic.GeoBlazor.Core.sln -c Release dotnet build src/dymaptic.GeoBlazor.Core.sln -c Debug -# Build TypeScript/JavaScript (from src/dymaptic.GeoBlazor.Core/) -pwsh esBuild.ps1 -c Debug -pwsh esBuild.ps1 -c Release +# Build TypeScript/JavaScript (from Core repo root) +dotnet ./build/ESBuild.cs -- -c Debug +dotnet ./build/ESBuild.cs -- -c Release ``` ### Test @@ -77,7 +77,7 @@ pwsh bumpVersion.ps1 -test 1.2.3 # Test version bump ### Development ```bash # Clear ESBuild locks if build is stuck -pwsh esBuildClearLocks.ps1 +dotnet ./build-tools/ESBuildClearLocks.dll # Watch TypeScript changes (from src/dymaptic.GeoBlazor.Core/) npm run watchBuild @@ -106,7 +106,7 @@ npm install (from src/dymaptic.GeoBlazor.Core/) Known issue: ESBuild compilation conflicts with MSBuild static file analysis may cause intermittent build errors when building projects with project references to Core. This is tracked with Microsoft. ### Development Workflow -1. Changes to TypeScript require running ESBuild (automatic via source generator or manual via `esBuild.ps1`). You should see a popup dialog when this is happening automatically from the source generator. +1. Changes to TypeScript require running ESBuild (automatic via source generator or manual via `ESBuild.cs`). You should see a popup dialog when this is happening automatically from the source generator. 2. Browser cache should be disabled when testing JavaScript changes 3. Generated code (`.gb.*` files) should never be edited directly. Instead, move code into the matching hand-editable file to "override" the generated code. 4. When adding new components, use the Code Generator in the parent CodeGen repository diff --git a/Directory.Build.props b/Directory.Build.props index 0c202811e..a17678e13 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,10 +1,11 @@ + enable enable - 5.0.0.13 + 5.0.0.38 true Debug;Release;SourceGen Highlighting AnyCPU @@ -22,5 +23,6 @@ + \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets index 5a086a0b9..ff2f4fea7 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 55b675c51..832b91c9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,8 +27,10 @@ COPY ./*.ps1 ./ COPY ./Directory.Build.* ./ COPY ./.gitignore ./.gitignore COPY ./nuget.config ./nuget.config +COPY ./build-tools ./build-tools +COPY ./build-scripts/ScriptBuilder.cs ./build-scripts/ScriptBuilder.cs -RUN pwsh -Command "./GeoBlazorBuild.ps1" +RUN dotnet ./build-tools/GeoBlazorBuild.dll -v current COPY ./test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared/dymaptic.GeoBlazor.Core.Test.Blazor.Shared.csproj ./test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared.csproj COPY ./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.csproj ./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.csproj @@ -40,15 +42,14 @@ RUN dotnet restore ./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor COPY ./test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared ./test/dymaptic.GeoBlazor.Core.Test.Blazor.Shared COPY ./test/dymaptic.GeoBlazor.Core.Test.WebApp ./test/dymaptic.GeoBlazor.Core.Test.WebApp -RUN pwsh -Command './buildAppSettings.ps1 \ - -ArcGISApiKey $env:ARCGIS_API_KEY \ - -LicenseKey $env:GEOBLAZOR_LICENSE_KEY \ - -OutputPaths @( \ - "./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.Client/wwwroot/appsettings.json", \ - "./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.Client/wwwroot/appsettings.Production.json", \ - "./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/appsettings.json", \ - "./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/appsettings.Production.json") \ - -WfsServers $env:WFS_SERVERS' +RUN dotnet ./build-tools/BuildAppSettings.dll \ + -k "$ARCGIS_API_KEY" \ + -l "$GEOBLAZOR_LICENSE_KEY" \ + -o "./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.Client/wwwroot/appsettings.json" \ + -o "./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp.Client/wwwroot/appsettings.Production.json" \ + -o "./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/appsettings.json" \ + -o "./test/dymaptic.GeoBlazor.Core.Test.WebApp/dymaptic.GeoBlazor.Core.Test.WebApp/appsettings.Production.json" \ + -w "$WFS_SERVERS" # Build from source with debug symbols for code coverage # UsePackageReference=false builds GeoBlazor from source instead of NuGet diff --git a/GeoBlazorBuild.ps1 b/GeoBlazorBuild.ps1 deleted file mode 100644 index ffaafb513..000000000 --- a/GeoBlazorBuild.ps1 +++ /dev/null @@ -1,541 +0,0 @@ -# This is the primary build script for GeoBlazor and GeoBlazor Pro. -param( - [switch]$Pro, - [switch][Alias("pub")]$PublishVersion, - [switch][Alias("obf")]$Obfuscate, - [switch][Alias("docs")]$GenerateDocs, - [switch][Alias("xml")]$GenerateXmlComments, - [switch][Alias("pkg")]$Package, - [switch][Alias("bl")]$Binlog, - [switch][Alias("h")]$Help, - [string][Alias("v")]$Version, - [string][Alias("c")]$Configuration = "Release", - [string][Alias("vc")]$ValidatorConfig = "Release", - [string][Alias("su")]$ServerUrl = "https://licensing.dymaptic.com", - [int][Alias("retries")]$BuildRetries = 5) - -if ($Help) { - Write-Host "GeoBlazor Build Script" - Write-Host "" - Write-Host "Parameters:" - Write-Host " -Pro (-pro) Build GeoBlazor Pro as well as Core (default is false)" - Write-Host " -PublishVersion (-pub) Truncate the build version to 3 digits for NuGet (default is false)" - Write-Host " -Obfuscate (-obf) Obfuscate the Pro license validation logic (default is false)" - Write-Host " -GenerateDocs (-docs) Generate documentation files for the docs site (default is false)" - Write-Host " -GenerateXmlComments (-xml) Generate the XML comments that provide intellisense when using the library in an IDE" - Write-Host " -Package (-pkg) Create NuGet packages (default is false)" - Write-Host " -Binlog (-bl) Generate MSBuild binary log files (default is false)" - Write-Host " -Version (-v) Specify a custom version number (default is to auto-increment the current build version)" - Write-Host " -Configuration (-c) Build configuration (default is 'Release')" - Write-Host " -ValidatorConfig (-v) Validator build configuration (default is 'Release')" - Write-Host " -ServerUrl (-su) License server URL (default is 'https://licensing.dymaptic.com')" - Write-Host " -BuildRetries (-retries) Number of times to retry the build on failure (default is 5)" - Write-Host " -Help (-h) Display this help message" - exit 0 -} - -if ($GenerateDocs) { - $GenerateXmlComments = $true -} - -Write-Host "Starting GeoBlazor Build Script" -Write-Host "Pro Build: $Pro" -Write-Host "Set Nuget Publish Version Build: $PublishVersion" -Write-Host "Obfuscate Pro Build: $Obfuscate" -Write-Host "Generate Documentation Files: $GenerateDocs" -Write-Host "Generate XML Documentation: $GenerateXmlComments" -Write-Host "Build Package: $($Package -eq $true)" -Write-Host "Version: $Version" -Write-Host "Configuration: $Configuration" -Write-Host "Validator Configuration: $ValidatorConfig" -Write-Host "License Server URL: $ServerUrl" - -$scriptStartTime = Get-Date - -$CoreRepoRoot = $PSScriptRoot -$ProRepoRoot = (Join-Path -Path $PSScriptRoot "..") -$BinlogFlag = $Binlog ? ' -bl' : '' - -try { - ## Paths - $CorePropsPath = Join-Path -Path $CoreRepoRoot "Directory.Build.props" - $CoreProjectPath = Join-Path -Path $CoreRepoRoot "src/dymaptic.GeoBlazor.Core" - $ProPropsPath = Join-Path -Path $ProRepoRoot "Directory.Build.props" - $ProProjectPath = Join-Path -Path $ProRepoRoot "src/dymaptic.GeoBlazor.Pro" - $ValidatorProjectPath = Join-Path -Path $ProRepoRoot "src/dymaptic.GeoBlazor.Pro.Validator" - $AssetsPath = Join-Path -Path $CoreProjectPath "wwwroot/assets" - - $OtherConfiguration = if ($Configuration.ToLowerInvariant() -eq "release") { "Debug" } else { "Release" } - - # Set up locks - $CoreLockFilePath = Join-Path -Path $CoreProjectPath "esBuild.$OtherConfiguration.lock" - $ProLockFilePath = Join-Path -Path $ProProjectPath "esProBuild.$OtherConfiguration.lock" - - if ((Test-Path $CoreLockFilePath) -or (Test-Path $ProLockFilePath)) { - # wait for the lock to be released - Write-Host "Another instance of the esBuild scripts are already running, please wait." - while ((Test-Path $CoreLockFilePath) -or (Test-Path $ProLockFilePath)) { - Start-Sleep -Seconds 1 - Write-Host -NoNewline "." - if ($scriptStartTime.AddMinutes(1) -lt (Get-Date)) { - if (Test-Path $CoreLockFilePath) { - Write-Host "`nLock file $CoreLockFilePath still exists after 1 minute, removing stale lock." - Remove-Item -Path $CoreLockFilePath -Force - } - if (Test-Path $ProLockFilePath) { - Write-Host "`nLock file $ProLockFilePath still exists after 1 minute, removing stale lock." - Remove-Item -Path $ProLockFilePath -Force - } - } - } - Write-Host "Lock released, continuing..." - } - - ## Create lock files to prevent multiple instances of the script from running at the same time - New-Item -Path $CoreLockFilePath - if ($Pro -eq $true) { - New-Item -Path $ProLockFilePath - } - - # Set Environment Variables - $env:PipelineBuild = "true" - - $Step = 1 - - ## Clean out old build artifacts - $StepStartTime = Get-Date - Write-Host "" - Write-Host "$Step. Cleaning old build artifacts" -BackgroundColor DarkMagenta -ForegroundColor White -NoNewline - Write-Host "" - Write-Host "" - dotnet clean (Join-Path $CoreProjectPath dymaptic.GeoBlazor.Core.csproj) /p:PipelineBuild=true - Get-ChildItem -Path (Join-Path $CoreProjectPath "bin") -Recurse -Force | Remove-Item -Recurse -Force - Get-ChildItem -Path (Join-Path $CoreProjectPath "obj") -Recurse -Force | Remove-Item -Recurse -Force - if (Test-Path (Join-Path $CoreProjectPath "wwwroot/js")) { - Get-ChildItem -Path (Join-Path $CoreProjectPath "wwwroot/js") -Recurse -Force | Remove-Item -Recurse -Force - } - if (Test-Path (Join-Path $CoreProjectPath "node_modules")) { - Get-ChildItem -Path (Join-Path $CoreProjectPath "node_modules") -Recurse -Force | Remove-Item -Recurse -Force - } - - if ($Pro -eq $true) { - dotnet clean (Join-Path $ProProjectPath dymaptic.GeoBlazor.Pro.csproj) /p:PipelineBuild=true - Get-ChildItem -Path (Join-Path $ProProjectPath "bin") -Recurse -Force | Remove-Item -Recurse -Force - Get-ChildItem -Path (Join-Path $ProProjectPath "obj") -Recurse -Force | Remove-Item -Recurse -Force - Get-ChildItem -Path (Join-Path $ProProjectPath "obf") -Recurse -Force | Remove-Item -Recurse -Force - if (Test-Path (Join-Path $ProProjectPath "build/resources")) { - Get-ChildItem -Path (Join-Path $ProProjectPath "build/resources") -Recurse -Force | Remove-Item -Recurse -Force - } - if (Test-Path (Join-Path $ProProjectPath "wwwroot/js")) { - Get-ChildItem -Path (Join-Path $ProProjectPath "wwwroot/js") -Recurse -Force | Remove-Item -Recurse -Force - } - if (Test-Path (Join-Path $ProProjectPath "node_modules")) { - Get-ChildItem -Path (Join-Path $ProProjectPath "node_modules") -Recurse -Force | Remove-Item -Recurse -Force - } - if (Test-Path $ValidatorProjectPath) { - dotnet clean (Join-Path $ValidatorProjectPath dymaptic.GeoBlazor.Pro.V.csproj) - Get-ChildItem -Path (Join-Path $ValidatorProjectPath "bin") -Recurse -Force | Remove-Item -Recurse -Force - Get-ChildItem -Path (Join-Path $ValidatorProjectPath "obj") -Recurse -Force | Remove-Item -Recurse -Force - Get-ChildItem -Path (Join-Path $ValidatorProjectPath "obf") -Recurse -Force | Remove-Item -Recurse -Force - } - - } - Write-Host "Step $Step completed in $( (Get-Date) - $StepStartTime )." -BackgroundColor Yellow -ForegroundColor Black -NoNewline - Write-Host "" - - $Step++ - - ## Delete old assets - from prior to 4.2.x - if (Test-Path $AssetsPath) { - $StepStartTime = Get-Date - Write-Host "" - Write-Host "$Step. Deleting old assets at $AssetsPath" -BackgroundColor DarkMagenta -ForegroundColor White -NoNewline - Write-Host "" - Write-Host "" - Get-ChildItem -Path $AssetsPath -Recurse | Remove-Item -Recurse -Force - Write-Host "Step $Step completed in $( (Get-Date) - $StepStartTime )." -BackgroundColor Yellow -ForegroundColor Black -NoNewline - Write-Host "" - - $Step++ - } - - $CustomVersionSet = $null -ne $Version -and $Version -ne "" - - if ($CustomVersionSet -ne $true) { - $StepStartTime = Get-Date - Set-Location $CoreRepoRoot - - ## Set the version number in Directory.Build.props - Write-Host "" - Write-Host "$Step. Updating Library Versions" -BackgroundColor DarkMagenta -ForegroundColor White -NoNewline - Write-Host "" - Write-Host "" - - [xml]$CoreProps = [xml](Get-Content $CorePropsPath) - $CurrentCoreVersion = $CoreProps.Project.PropertyGroup.CoreVersion - - if ($PublishVersion) { - $NewCoreVersion = ./bumpVersion.ps1 -publish - } else { - $NewCoreVersion = ./bumpVersion.ps1 - } - - if ($Pro -eq $true) { - [xml]$ProProps = [xml](Get-Content $ProPropsPath) - $CurrentProVersion = $ProProps.Project.PropertyGroup.ProVersion - - if ($PublishVersion) { - $Version = ./bumpVersion.ps1 -publish -pro - } else { - $Version = ./bumpVersion.ps1 -pro - } - - if ($NewCoreVersion -gt $Version -and $CurrentCoreVersion -gt $CurrentProVersion) { - $Version = $NewCoreVersion - } elseif ($NewCoreVersion -lt $Version) { - "Core version ($NewCoreVersion) and Pro version ($Version) do not match after bumping. Please ensure both versions are the same in Directory.Build.props." - } - - if ($CurrentProVersion -eq $Version) { - Write-Host "Pro Version is already set to $Version, no update needed." - } - else - { - Write-Host "Updating Pro Version from $CurrentProVersion to $Version" - $ProProps.Project.PropertyGroup.ProVersion = $Version - $ProProps.Save($ProPropsPath) - } - } else { - $Version = $NewCoreVersion - } - - if ($CurrentCoreVersion -eq $Version) { - Write-Host "Core Version is already set to $Version, no update needed." - } - else { - Write-Host "Updating Core Version from $CurrentCoreVersion to $Version" - $CoreProps.Project.PropertyGroup.CoreVersion = $Version - $CoreProps.Save($CorePropsPath) - } - - Write-Host "Step $Step completed in $( (Get-Date) - $StepStartTime )." -BackgroundColor Yellow -ForegroundColor Black -NoNewline - Write-Host "" - - $Step++ - } - - Set-Location $CoreProjectPath - - $StepStartTime = Get-Date - - $CoreLockFilePath = Join-Path -Path $CoreProjectPath "esBuild.$Configuration.lock" - - $Locked = $null -ne (Get-Item -Path $CoreLockFilePath -EA 0) - - if ($Locked) { - # wait for the lock to be released - Write-Host "Another instance of the esBuild script is already running, please wait." - while (Test-Path $CoreLockFilePath) { - Start-Sleep -Seconds 1 - Write-Host -NoNewline "." - } - Write-Host "Lock released, continuing..." - } - - Write-Host "" - Write-Host "$Step. Building Core JavaScript" -BackgroundColor DarkMagenta -ForegroundColor White -NoNewline - Write-Host "" - Write-Host "" - ./esBuild.ps1 -c $Configuration - if ($LASTEXITCODE -ne 0) { - Write-Host "ERROR: esBuild.ps1 failed with exit code $LASTEXITCODE. Exiting." -ForegroundColor Red - exit 1 - } - - # Verify JavaScript files were created - $CoreJsPath = Join-Path -Path $CoreProjectPath "wwwroot/js" - if (-not (Test-Path $CoreJsPath) -or (Get-ChildItem -Path $CoreJsPath -Filter "*.js" | Measure-Object).Count -eq 0) { - Write-Host "WARNING: Core JavaScript files not found at $CoreJsPath, waiting..." -ForegroundColor Yellow - Start-Sleep -Seconds 2 - if (-not (Test-Path $CoreJsPath) -or (Get-ChildItem -Path $CoreJsPath -Filter "*.js" | Measure-Object).Count -eq 0) { - Write-Host "ERROR: Core JavaScript files still not found after waiting. Exiting." -ForegroundColor Red - exit 1 - } - } - - Write-Host "Step $Step completed in $( (Get-Date) - $StepStartTime )." -BackgroundColor Yellow -ForegroundColor Black -NoNewline - Write-Host "" - - $Step++ - - $StepStartTime = Get-Date - Write-Host "" - Write-Host "$Step. Restoring .Net Packages" -BackgroundColor DarkMagenta -ForegroundColor White -NoNewline - Write-Host "" - Write-Host "" - dotnet restore /p:PipelineBuild=true - Write-Host "Step $Step completed in $( (Get-Date) - $StepStartTime )." -BackgroundColor Yellow -ForegroundColor Black -NoNewline - Write-Host "" - - $Step++ - - $StepStartTime = Get-Date - Write-Host "" - Write-Host "$Step. Building Core Project and NuGet Package" -BackgroundColor DarkMagenta -ForegroundColor White -NoNewline - Write-Host "" - Write-Host "" - - # double-escape line breaks - $CoreBuild = "dotnet build dymaptic.GeoBlazor.Core.csproj --no-restore `` - -c $Configuration `` - /p:PipelineBuild=true `` - /p:GenerateDocs=$($GenerateDocs.ToString().ToLower()) `` - /p:GenerateXmlComments=$($GenerateXmlComments.ToString().ToLower()) `` - /p:CoreVersion=$Version `` - /p:GeneratePackage=$($Package.ToString().ToLower()) $BinlogFlag 2>&1" - Write-Host "Executing '$CoreBuild'" - - # sometimes the build fails due to a Microsoft bug, retry a few times - for ($i = 1; $i -lt $BuildRetries; $i++) { - try - { - Invoke-Expression $CoreBuild | Tee-Object -Variable Build - if ($LASTEXITCODE -ne 0) { - Write-Host "ERROR: Core Build command failed with exit code $LASTEXITCODE. Exiting." - $HasError = true - } else { - $HasError = (($Build -match "[1-9][0-9]* [Ee]rror(s)") -or ($Build -match "Build FAILED")) - if ($HasError -eq $false) - { - break - } - } - } - catch - { - $HasError = $true - Write-Host "Build attempt $i of $BuildRetries failed with exception: $_" - } - - Write-Host "Build attempt $i of $BuildRetries failed." - if ($i -lt $BuildRetries -1) - { - Write-Host "Waiting 2 seconds before retrying..." - Start-Sleep -Seconds 2 - } - } - - if ($HasError -eq $true) - { - exit 1 - } - - if ($Package -eq $true) { - # Copy generated NuGet package to script root - $CoreNupkg = Get-ChildItem -Path "bin/$Configuration" -Filter "*.nupkg" -Recurse | Sort-Object LastWriteTime -Descending | Select-Object -First 1 - if ($CoreNupkg) { - Copy-Item -Path $CoreNupkg.FullName -Destination $CoreRepoRoot -Force - Write-Host "Copied $($CoreNupkg.Name) to $CoreRepoRoot" - } - } - - Write-Host "Step $Step completed in $( (Get-Date) - $StepStartTime )." -BackgroundColor Yellow -ForegroundColor Black -NoNewline - Write-Host "" - - $Step++ - - if ($Pro -eq $true) { - Set-Location $ProProjectPath - - $StepStartTime = Get-Date - Write-Host "" - Write-Host "$Step. Restoring .NET Packages" -BackgroundColor DarkMagenta -ForegroundColor White -NoNewline - Write-Host "" - Write-Host "" - dotnet restore /p:PipelineBuild=true - Write-Host "Step $Step completed in $( (Get-Date) - $StepStartTime )." -BackgroundColor Yellow -ForegroundColor Black -NoNewline - Write-Host "" - - $Step++ - - # Set OptOutFromObfuscation before building Validator or Pro (needed for both) - $OptOutFromObfuscation = $Obfuscate -eq $false - - if (Test-Path $ValidatorProjectPath) { - ## Build the Validator MSBuild task - $StepStartTime = Get-Date - Set-Location $ValidatorProjectPath - Write-Host "" - Write-Host "$Step. Building Validator project in configuration $ValidatorConfig" -BackgroundColor DarkMagenta -ForegroundColor White -NoNewline - Write-Host "" - Write-Host "" - - # Set the ServerUrls in the Validator project - $ServerUrl = $ServerUrl.TrimEnd('/') - Write-Host "Setting License Server Url to $ServerUrl" - $ValidatorContent = Get-Content 'DevBuildValidator.cs' -Raw; - $ValidatorContent = $ValidatorContent -replace 'public string SU \{ get; set; \} = null!;', "public string SU { get; set; } = `"$ServerUrl/api/validate/v4`";" - Set-Content 'DevBuildValidator.cs' -Value $ValidatorContent -NoNewline -Force -Encoding UTF8 - if ($IsMacOS) { - & sync - } - Start-Sleep -Milliseconds 500 - $ValidatorContent = Get-Content 'DevBuildValidator.cs' -Raw; - if ($ValidatorContent -notmatch [regex]::Escape("public string SU { get; set; } = `"$ServerUrl/api/validate/v4`";")) { - throw "Failed to set ServerUrl in DevBuildValidator.cs" - } - $ValidatorContent = Get-Content 'PublishTaskValidator.cs' -Raw; - $ValidatorContent = $ValidatorContent -replace 'public string SU \{ get; set; \} = null!;', "public string SU { get; set; } = `"$ServerUrl/api/validate/v4/publish`";" - Set-Content 'PublishTaskValidator.cs' -Value $ValidatorContent -NoNewline -Force -Encoding UTF8 - if ($IsMacOS) { - & sync - } - Start-Sleep -Milliseconds 500 - $ValidatorContent = Get-Content 'PublishTaskValidator.cs' -Raw; - if ($ValidatorContent -notmatch [regex]::Escape("public string SU { get; set; } = `"$ServerUrl/api/validate/v4/publish`";")) { - throw "Failed to set ServerUrl in PublishTaskValidator.cs" - } - - dotnet build dymaptic.GeoBlazor.Pro.V.csproj /p:OptOutFromObfuscation=$($OptOutFromObfuscation.ToString().ToLower()) ` - /p:ProVersion=$Version -c $ValidatorConfig $BinlogFlag 2>&1 | Tee-Object -Variable Build - - # Restore the ServerUrls in the Validator project - $ValidatorContent = Get-Content 'DevBuildValidator.cs' -Raw; - $ValidatorContent = $ValidatorContent -replace 'public string SU \{ get; set; \} = ".*";', 'public string SU { get; set; } = null!;' - Set-Content 'DevBuildValidator.cs' -Value $ValidatorContent -NoNewline - $ValidatorContent = Get-Content 'PublishTaskValidator.cs' -Raw; - $ValidatorContent = $ValidatorContent -replace 'public string SU \{ get; set; \} = ".*";', 'public string SU { get; set; } = null!;' - Set-Content 'PublishTaskValidator.cs' -Value $ValidatorContent -NoNewline - $HasError = (($Build -match "[1-9][0-9]* [Ee]rror(s)") -or ($Build -match "Build FAILED")) - if ($HasError -eq $true) { - exit 1 - } - Write-Host "Step $Step completed in $( (Get-Date) - $StepStartTime )." -BackgroundColor Yellow -ForegroundColor Black -NoNewline - Write-Host "" - - $Step++ - } - - Set-Location $ProProjectPath - - $StepStartTime = Get-Date - - if (Test-Path $ProLockFilePath) { - # wait for the lock to be released - Write-Host "Another instance of the esBuild scripts are already running, please wait." - while (Test-Path $ProLockFilePath) { - Start-Sleep -Seconds 1 - Write-Host -NoNewline "." - } - Write-Host "Lock released, continuing..." - } - - Write-Host "" - Write-Host "$Step. Building Pro JavaScript" -BackgroundColor DarkMagenta -ForegroundColor White -NoNewline - Write-Host "" - Write-Host "" - ./esProBuild.ps1 -c $Configuration - if ($LASTEXITCODE -ne 0) { - Write-Host "ERROR: esProBuild.ps1 failed with exit code $LASTEXITCODE. Exiting." -ForegroundColor Red - exit 1 - } - - # Verify JavaScript files were created - $ProJsPath = Join-Path -Path $ProProjectPath "wwwroot/js" - if (-not (Test-Path $ProJsPath) -or (Get-ChildItem -Path $ProJsPath -Filter "*.js" | Measure-Object).Count -eq 0) { - Write-Host "WARNING: Pro JavaScript files not found at $ProJsPath, waiting..." -ForegroundColor Yellow - Start-Sleep -Seconds 2 - if (-not (Test-Path $ProJsPath) -or (Get-ChildItem -Path $ProJsPath -Filter "*.js" | Measure-Object).Count -eq 0) { - Write-Host "ERROR: Pro JavaScript files still not found after waiting. Exiting." -ForegroundColor Red - exit 1 - } - } - - Write-Host "Step $Step completed in $( (Get-Date) - $StepStartTime )." -BackgroundColor Yellow -ForegroundColor Black -NoNewline - Write-Host "" - - $Step++ - - $StepStartTime = Get-Date - Write-Host "" - Write-Host "$Step. Building Pro project and package" -BackgroundColor DarkMagenta -ForegroundColor White -NoNewline - Write-Host "" - Write-Host "" - - # double-escape line breaks - $ProBuild = "dotnet build dymaptic.GeoBlazor.Pro.csproj --no-restore `` - -c $Configuration `` - /p:PipelineBuild=true `` - /p:GenerateDocs=$($GenerateDocs.ToString().ToLower()) `` - /p:GenerateXmlComments=$($GenerateXmlComments.ToString().ToLower()) `` - /p:CoreVersion=$Version `` - /p:ProVersion=$Version `` - /p:OptOutFromObfuscation=$($OptOutFromObfuscation.ToString().ToLower()) `` - /p:GeneratePackage=$($Package.ToString().ToLower()) $BinlogFlag 2>&1" - Write-Host "Executing '$ProBuild'" - - # sometimes the build fails due to a Microsoft bug, retry a few times - for ($i = 1; $i -lt $BuildRetries; $i++) { - try - { - Invoke-Expression $ProBuild | Tee-Object -Variable Build - if ($LASTEXITCODE -ne 0) { - Write-Host "ERROR: Pro Build command failed with exit code $LASTEXITCODE. Exiting." - $HasError = true - } else { - $HasError = (($Build -match "[1-9][0-9]* [Ee]rror(s)") -or ($Build -match "Build FAILED")) - if ($HasError -eq $false) - { - break - } - } - } - catch - { - $HasError = $true - Write-Host "Build attempt $i of $BuildRetries failed with exception: $_" - } - - Write-Host "Build attempt $i of $BuildRetries failed." - if ($i -lt $BuildRetries -1) - { - Write-Host "Waiting 2 seconds before retrying..." - Start-Sleep -Seconds 2 - } - } - - if ($HasError -eq $true) - { - exit 1 - } - - if ($Package -eq $true) { - # Copy generated NuGet package to script root - $ProNupkg = Get-ChildItem -Path "bin/$Configuration" -Filter "*.nupkg" -Recurse | Sort-Object LastWriteTime -Descending | Select-Object -First 1 - if ($ProNupkg) { - Copy-Item -Path $ProNupkg.FullName -Destination $CoreRepoRoot -Force - Write-Host "Copied $($ProNupkg.Name) to $CoreRepoRoot" - } - } - - Write-Host "Step $Step completed in $( (Get-Date) - $StepStartTime )." -BackgroundColor Yellow -ForegroundColor Black -NoNewline - Write-Host "" - } -} -catch { - Write-Host "An error occurred: $_" - exit 1 -} -finally { - $totalTime = (Get-Date) - $scriptStartTime - # Remove lock files - if (Test-Path $CoreLockFilePath) { - Remove-Item -Path $CoreLockFilePath -Force - } - if (Test-Path $ProLockFilePath) { - Remove-Item -Path $ProLockFilePath -Force - } - Write-Host "" - Write-Host "Total script execution time: $totalTime." -BackgroundColor DarkBlue -ForegroundColor White - Set-Location $PSScriptRoot -} diff --git a/badge_fullmethodcoverage.svg b/badge_fullmethodcoverage.svg index 1888c66f1..f80e7c08e 100644 --- a/badge_fullmethodcoverage.svg +++ b/badge_fullmethodcoverage.svg @@ -132,7 +132,7 @@ - 28.6%28.6% + 3%3% diff --git a/badge_linecoverage.svg b/badge_linecoverage.svg index f2be831f2..24ecd11fc 100644 --- a/badge_linecoverage.svg +++ b/badge_linecoverage.svg @@ -123,7 +123,7 @@ Coverage Coverage - 8.3%8.3% + 1.3%1.3% diff --git a/badge_methodcoverage.svg b/badge_methodcoverage.svg index 820772278..f06ecb44c 100644 --- a/badge_methodcoverage.svg +++ b/badge_methodcoverage.svg @@ -125,7 +125,7 @@ Coverage - 31.6%31.6% + 3.2%3.2% diff --git a/build-scripts/BuildAppSettings.cs b/build-scripts/BuildAppSettings.cs new file mode 100644 index 000000000..b0ea8c79e --- /dev/null +++ b/build-scripts/BuildAppSettings.cs @@ -0,0 +1,181 @@ +#!/usr/bin/env dotnet + +// Build AppSettings Script +// C# file-based app version of buildAppSettings.ps1 +// Generates appsettings.json files for test applications. +// +// Usage: dotnet BuildAppSettings.cs [options] +// -k, --api-key ArcGIS API key (required) +// -l, --license-key GeoBlazor license key (required) +// -o, --output Output path(s) for appsettings.json (required, can specify multiple) +// -d, --docs-url Documentation URL (default: https://docs.geoblazor.com) +// -b, --bypass-key API bypass key for samples (optional) +// -w, --wfs-servers Additional WFS server configuration JSON fragment (optional) +// -h, --help Display help message +// +// Example: +// dotnet BuildAppSettings.cs -k "your-arcgis-key" -l "your-license" -o "./appsettings.json" +// dotnet BuildAppSettings.cs -k "key" -l "license" -o "./app1/appsettings.json" -o "./app2/appsettings.json" + +using System.Text; + +string? arcGISApiKey = null; +string? licenseKey = null; +List outputPaths = []; +string docsUrl = "https://docs.geoblazor.com"; +string byPassApiKey = ""; +string wfsServers = ""; +bool help = false; + +// Parse command line arguments +for (int i = 0; i < args.Length; i++) +{ + string arg = args[i]; + switch (arg.ToLowerInvariant()) + { + case "-k": + case "--api-key": + if (i + 1 < args.Length) + { + arcGISApiKey = args[++i]; + } + break; + case "-l": + case "--license-key": + if (i + 1 < args.Length) + { + licenseKey = args[++i]; + } + break; + case "-o": + case "--output": + if (i + 1 < args.Length) + { + outputPaths.Add(args[++i]); + } + break; + case "-d": + case "--docs-url": + if (i + 1 < args.Length) + { + docsUrl = args[++i]; + } + break; + case "-b": + case "--bypass-key": + if (i + 1 < args.Length) + { + byPassApiKey = args[++i]; + } + break; + case "-w": + case "--wfs-servers": + if (i + 1 < args.Length) + { + wfsServers = args[++i]; + } + break; + case "-h": + case "--help": + help = true; + break; + } +} + +if (help) +{ + Console.WriteLine("Build AppSettings Script"); + Console.WriteLine("Generates appsettings.json files for test applications."); + Console.WriteLine(); + Console.WriteLine("Usage: dotnet BuildAppSettings.cs [options]"); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine(" -k, --api-key ArcGIS API key (required)"); + Console.WriteLine(" -l, --license-key GeoBlazor license key (required)"); + Console.WriteLine(" -o, --output Output path(s) for appsettings.json (required, can specify multiple)"); + Console.WriteLine(" -d, --docs-url Documentation URL (default: https://docs.geoblazor.com)"); + Console.WriteLine(" -b, --bypass-key API bypass key for samples (optional)"); + Console.WriteLine(" -w, --wfs-servers Additional WFS server configuration JSON fragment (optional)"); + Console.WriteLine(" -h, --help Display this help message"); + Console.WriteLine(); + Console.WriteLine("Examples:"); + Console.WriteLine(" dotnet BuildAppSettings.cs -k \"your-arcgis-key\" -l \"your-license\" -o \"./appsettings.json\""); + Console.WriteLine(" dotnet BuildAppSettings.cs -k \"key\" -l \"license\" -o \"./app1/appsettings.json\" -o \"./app2/appsettings.json\""); + return 0; +} + +// Validate required parameters +if (string.IsNullOrWhiteSpace(arcGISApiKey)) +{ + Console.Error.WriteLine("Error: ArcGIS API key is required. Use -k or --api-key to specify."); + return 1; +} + +if (string.IsNullOrWhiteSpace(licenseKey)) +{ + Console.Error.WriteLine("Error: GeoBlazor license key is required. Use -l or --license-key to specify."); + return 1; +} + +if (outputPaths.Count == 0) +{ + Console.Error.WriteLine("Error: At least one output path is required. Use -o or --output to specify."); + return 1; +} + +// Build the appsettings JSON content +var sb = new StringBuilder(); +sb.AppendLine("{"); +sb.AppendLine($" \"ArcGISApiKey\": \"{EscapeJsonString(arcGISApiKey)}\","); +sb.AppendLine(" \"GeoBlazor\": {"); +sb.AppendLine($" \"LicenseKey\": \"{EscapeJsonString(licenseKey)}\""); +sb.AppendLine(" },"); +sb.AppendLine($" \"DocsUrl\": \"{EscapeJsonString(docsUrl)}\","); +sb.Append($" \"ByPassApiKey\": \"{EscapeJsonString(byPassApiKey)}\""); + +// Add WFS servers if provided +if (!string.IsNullOrWhiteSpace(wfsServers)) +{ + sb.AppendLine(","); + sb.Append($" {wfsServers}"); +} + +sb.AppendLine(); +sb.AppendLine("}"); + +string appSettingsContent = sb.ToString(); + +// Write to each target path +foreach (string path in outputPaths) +{ + try + { + string? directory = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + File.WriteAllText(path, appSettingsContent, Encoding.UTF8); + Console.WriteLine($"Created: {path}"); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error writing to {path}: {ex.Message}"); + return 1; + } +} + +Console.WriteLine("AppSettings files generated successfully."); +return 0; + +// Helper function to escape JSON string values +static string EscapeJsonString(string value) +{ + return value + .Replace("\\", "\\\\") + .Replace("\"", "\\\"") + .Replace("\n", "\\n") + .Replace("\r", "\\r") + .Replace("\t", "\\t"); +} diff --git a/build-scripts/BuildTemplates.cs b/build-scripts/BuildTemplates.cs new file mode 100644 index 000000000..82e25da6c --- /dev/null +++ b/build-scripts/BuildTemplates.cs @@ -0,0 +1,609 @@ +#!/usr/bin/env dotnet +#:property PublishAot=false + +// Build Templates Script +// C# file-based app version of buildTemplates.ps1 +// Updates and builds GeoBlazor project templates +// +// Usage: dotnet BuildTemplates.cs [options] +// -i, --install Install templates after building +// -c, --core-version Override Core version (default: read from Directory.Build.props) +// -p, --pro-version Override Pro version (default: read from Directory.Build.props) +// -t, --templates-dir Templates directory (default: auto-detect from script location) +// -h, --help Display help message +// +// Example: +// dotnet BuildTemplates.cs +// dotnet BuildTemplates.cs -i +// dotnet BuildTemplates.cs -c 5.0.0 -p 5.0.0 -i + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; +using System.Xml.Linq; + +// Target frameworks to support +int[] targetFrameworks = [8, 9, 10]; + +// Package definitions +string[] packages = +[ + "Microsoft.Maui.Controls", + "Microsoft.AspNetCore.Components.WebView.Maui", + "Microsoft.Extensions.Configuration.Abstractions", + "Microsoft.Extensions.Logging.Debug", + "Microsoft.AspNetCore.Components.Web", + "Microsoft.AspNetCore.Components.WebAssembly.Server", + "Microsoft.AspNetCore.Components.WebAssembly", + "Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore", + "Microsoft.AspNetCore.Identity.EntityFrameworkCore", + "Microsoft.EntityFrameworkCore.Tools", + "Microsoft.EntityFrameworkCore.SqlServer", + "Microsoft.AspNetCore.Components.WebAssembly.Authentication", + "Microsoft.AspNetCore.Components.WebAssembly.DevServer", + "Microsoft.Authentication.WebAssembly.Msal" +]; + +// Package to symbol name mapping +Dictionary packageToSymbol = new() +{ + ["Microsoft.Maui.Controls"] = "MauiControls", + ["Microsoft.AspNetCore.Components.WebView.Maui"] = "WebViewMaui", + ["Microsoft.Extensions.Configuration.Abstractions"] = "ConfigurationAbstractions", + ["Microsoft.Extensions.Logging.Debug"] = "LoggingDebug", + ["Microsoft.AspNetCore.Components.Web"] = "ComponentsWeb", + ["Microsoft.AspNetCore.Components.WebAssembly.Server"] = "WebAssemblyServer", + ["Microsoft.AspNetCore.Components.WebAssembly"] = "ComponentsWebAssembly", + ["Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore"] = "DiagnosticsEntityFrameworkCore", + ["Microsoft.AspNetCore.Identity.EntityFrameworkCore"] = "IdentityEntityFrameworkCore", + ["Microsoft.EntityFrameworkCore.Tools"] = "EntityFrameworkCoreTools", + ["Microsoft.EntityFrameworkCore.SqlServer"] = "EntityFrameworkCoreSqlServer", + ["Microsoft.AspNetCore.Components.WebAssembly.Authentication"] = "WebAssemblyAuthentication", + ["Microsoft.AspNetCore.Components.WebAssembly.DevServer"] = "WebAssemblyDevServer", + ["Microsoft.Authentication.WebAssembly.Msal"] = "WebAssemblyMsal" +}; + +// Parse command line arguments +bool install = false; +string? coreVersionOverride = null; +string? proVersionOverride = null; +string? templatesDirOverride = null; +bool help = false; + +for (int i = 0; i < args.Length; i++) +{ + string arg = args[i].ToLowerInvariant(); + switch (arg) + { + case "-i": + case "--install": + install = true; + break; + case "-c": + case "--core-version": + if (i + 1 < args.Length) + { + coreVersionOverride = args[++i]; + } + break; + case "-p": + case "--pro-version": + if (i + 1 < args.Length) + { + proVersionOverride = args[++i]; + } + break; + case "-t": + case "--templates-dir": + if (i + 1 < args.Length) + { + templatesDirOverride = args[++i]; + } + break; + case "-h": + case "--help": + help = true; + break; + } +} + +if (help) +{ + Console.WriteLine("Build Templates Script"); + Console.WriteLine("Updates and builds GeoBlazor project templates."); + Console.WriteLine(); + Console.WriteLine("Usage: dotnet BuildTemplates.cs [options]"); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine(" -i, --install Install templates after building"); + Console.WriteLine(" -c, --core-version Override Core version"); + Console.WriteLine(" -p, --pro-version Override Pro version"); + Console.WriteLine(" -t, --templates-dir Templates directory path"); + Console.WriteLine(" -h, --help Display this help message"); + Console.WriteLine(); + Console.WriteLine("Examples:"); + Console.WriteLine(" dotnet BuildTemplates.cs"); + Console.WriteLine(" dotnet BuildTemplates.cs -i"); + Console.WriteLine(" dotnet BuildTemplates.cs -c 5.0.0 -p 5.0.0 -i"); + return 0; +} + +string currentDir = Environment.CurrentDirectory; +string scriptDir = GetScriptDirectory(); + +// Determine templates directory +string templatesDir = templatesDirOverride ?? Path.GetFullPath(Path.Combine(scriptDir, "..", "..", "templates")); + +if (!Directory.Exists(templatesDir)) +{ + Console.Error.WriteLine($"Error: Templates directory not found at {templatesDir}"); + return 1; +} + +// Determine versions +string coreVersion = coreVersionOverride ?? string.Empty; +string proVersion = proVersionOverride ?? string.Empty; + +if (string.IsNullOrEmpty(coreVersion) || string.IsNullOrEmpty(proVersion)) +{ + string buildPropsPath = Path.Combine(templatesDir, "..", "Directory.Build.props"); + if (!File.Exists(buildPropsPath)) + { + Console.Error.WriteLine($"Error: Directory.Build.props not found at {buildPropsPath}"); + return 1; + } + + var buildProps = XDocument.Load(buildPropsPath); + string? propsVersion = buildProps.Descendants("ProVersion").FirstOrDefault()?.Value; + + if (string.IsNullOrEmpty(propsVersion)) + { + Console.Error.WriteLine("Error: Could not read ProVersion from Directory.Build.props"); + return 1; + } + + // Use ProVersion for both Core and Pro (intentional - they should match for production releases) + if (string.IsNullOrEmpty(coreVersion)) + { + coreVersion = propsVersion; + } + if (string.IsNullOrEmpty(proVersion)) + { + proVersion = propsVersion; + } +} + +Console.WriteLine("Updating GeoBlazor Templates"); +Console.WriteLine($"- Using Core Version: {coreVersion}"); +Console.WriteLine($"- Using Pro Version: {proVersion}"); +Console.WriteLine(); + +// Find all .csproj files in content folder +string contentDir = Path.Combine(templatesDir, "content"); +string[] projectFiles = Directory.GetFiles(contentDir, "*.csproj", SearchOption.AllDirectories); + +Console.WriteLine("Updating Project References in .csproj files"); + +// Use a 3-piece version for the package ref to make sure it references an actually deployed nuget version +string packageVersion = Regex.Replace(proVersion, @"^(\d+\.\d+\.\d+)\.\d+(.*)$", "$1$2"); + +foreach (string projectFile in projectFiles) +{ + string content = File.ReadAllText(projectFile); + string originalContent = content; + + // Update GeoBlazor Core and Pro versions + content = Regex.Replace(content, + @">(); +using var httpClient = new HttpClient(); +httpClient.DefaultRequestHeaders.Add("User-Agent", "GeoBlazor-Build-Script"); + +foreach (string package in packages) +{ + try + { + Console.WriteLine($"Getting versions for {package}..."); + var versions = await GetPackageVersionsAsync(httpClient, package, targetFrameworks); + packageVersionsCache[package] = versions; + foreach (int tf in targetFrameworks) + { + Console.WriteLine($" Found: net{tf}.0 = {versions[tf] ?? "(none)"}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error getting versions for package {package}: {ex.Message}"); + } +} + +// Update template.json files +Console.WriteLine(); +Console.WriteLine("Updating package versions in template.json files"); + +var templateConfigDirs = Directory.GetDirectories(contentDir, ".template.config", SearchOption.AllDirectories); +foreach (string configDir in templateConfigDirs) +{ + string templateJsonPath = Path.Combine(configDir, "template.json"); + if (!File.Exists(templateJsonPath)) + { + continue; + } + + Console.WriteLine($"Processing {templateJsonPath}"); + + string jsonText = File.ReadAllText(templateJsonPath); + bool modified = false; + + // Parse JSON as mutable DOM + JsonNode? rootNode = JsonNode.Parse(jsonText, documentOptions: new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip }); + if (rootNode is not JsonObject root) + { + Console.WriteLine(" Failed to parse JSON, skipping"); + continue; + } + + JsonObject? symbols = root["symbols"]?.AsObject(); + if (symbols is null) + { + Console.WriteLine(" No symbols section found, skipping"); + continue; + } + + int latestTf = targetFrameworks[^1]; + + // 1. Handle Framework symbol - add missing choices and update default + JsonObject? frameworkSymbol = symbols["Framework"]?.AsObject(); + if (frameworkSymbol is not null) + { + // Add missing framework choices + JsonArray? choices = frameworkSymbol["choices"]?.AsArray(); + if (choices is not null) + { + foreach (int tf in targetFrameworks) + { + string choiceValue = $"net{tf}.0"; + bool exists = choices.Any(c => c?["choice"]?.GetValue() == choiceValue); + if (!exists) + { + choices.Add(new JsonObject { ["choice"] = choiceValue }); + modified = true; + Console.WriteLine($" Added missing framework choice: {choiceValue}"); + } + } + } + + // Update default framework to latest + string? currentDefault = frameworkSymbol["defaultValue"]?.GetValue(); + string newDefault = $"net{latestTf}.0"; + if (currentDefault != newDefault) + { + frameworkSymbol["defaultValue"] = newDefault; + modified = true; + Console.WriteLine($" Updated default framework to {newDefault}"); + } + } + + // 2. Update package versions in cases for each package symbol + foreach (string package in packages) + { + if (!packageToSymbol.TryGetValue(package, out string? symbolName)) + { + continue; + } + + var versions = packageVersionsCache.GetValueOrDefault(package); + if (versions is null) + { + continue; + } + + // Check if this symbol exists in the template + JsonObject? packageSymbol = symbols[symbolName]?.AsObject(); + if (packageSymbol is null) + { + continue; + } + + // Get the cases array from parameters + JsonObject? parameters = packageSymbol["parameters"]?.AsObject(); + if (parameters is null) + { + continue; + } + + JsonArray? cases = parameters["cases"]?.AsArray(); + if (cases is null) + { + continue; + } + + foreach (int tf in targetFrameworks) + { + string? version = versions[tf]; + if (version is null) + { + continue; + } + + string condition = $"(Framework == \"net{tf}.0\")"; + + // Find existing case for this framework + JsonObject? existingCase = null; + foreach (var caseNode in cases) + { + if (caseNode is JsonObject caseObj && + caseObj["condition"]?.GetValue()?.Contains($"net{tf}.0") == true) + { + existingCase = caseObj; + break; + } + } + + if (existingCase is not null) + { + // Update existing case value + string? currentValue = existingCase["value"]?.GetValue(); + if (currentValue != version) + { + existingCase["value"] = version; + modified = true; + Console.WriteLine($" Updated {symbolName} for net{tf}.0 to {version}"); + } + } + else + { + // Add new case for this framework + var newCase = new JsonObject + { + ["condition"] = condition, + ["value"] = version + }; + cases.Add(newCase); + modified = true; + Console.WriteLine($" Added {symbolName} case for net{tf}.0 with version {version}"); + } + } + } + + if (modified) + { + var options = new JsonSerializerOptions + { + WriteIndented = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + File.WriteAllText(templateJsonPath, rootNode.ToJsonString(options)); + Console.WriteLine($" Saved changes to {Path.GetFileName(templateJsonPath)}"); + } + else + { + Console.WriteLine($" No changes needed"); + } +} + +// Replace .razor files in content with those from Pages +Console.WriteLine(); +Console.WriteLine("Updating .razor files in content with those from Pages folder"); +string pagesDir = Path.Combine(templatesDir, "Pages"); +if (Directory.Exists(pagesDir)) +{ + string[] razorFiles = Directory.GetFiles(pagesDir, "*.razor"); + foreach (string razorFile in razorFiles) + { + string fileName = Path.GetFileName(razorFile); + string[] matchingContentFiles = Directory.GetFiles(contentDir, fileName, SearchOption.AllDirectories); + foreach (string destFile in matchingContentFiles) + { + File.Copy(razorFile, destFile, true); + Console.WriteLine($" Copied {fileName} to {Path.GetRelativePath(templatesDir, destFile)}"); + } + } +} +else +{ + Console.WriteLine(" Pages folder not found, skipping razor file copy"); +} + +// Build the templates +Console.WriteLine(); +Console.WriteLine("Building GeoBlazor Templates project"); +string templateProjectPath = Path.Combine(templatesDir, "dymaptic.GeoBlazor.Templates.csproj"); + +int restoreResult = RunDotnet($"restore \"{templateProjectPath}\""); +if (restoreResult != 0) +{ + Console.Error.WriteLine("Error: dotnet restore failed"); + return restoreResult; +} + +int buildResult = RunDotnet( + $"build \"{templateProjectPath}\" -c Release --no-restore " + + $"/p:ProVersion={proVersion} /p:OptOutFromCoreEsBuild=true " + + "/p:OptOutFromProEsBuild=true /p:GenerateDocs=false /p:GeneratePackage=false"); + +if (buildResult != 0) +{ + Console.Error.WriteLine("Error: dotnet build failed"); + return buildResult; +} + +// Install templates if requested +if (install) +{ + Console.WriteLine(); + Console.WriteLine("Installing templates..."); + + RunDotnet("new uninstall dymaptic.GeoBlazor.Templates"); + + string binDir = Path.Combine(templatesDir, "bin", "Release"); + string packageName = $"dymaptic.GeoBlazor.Templates.{proVersion}.nupkg"; + string packagePath = Path.Combine(binDir, packageName); + + if (!File.Exists(packagePath)) + { + Console.Error.WriteLine($"Error: Package not found at {packagePath}"); + return 1; + } + + int installResult = RunDotnet($"new install \"{packagePath}\""); + if (installResult != 0) + { + Console.Error.WriteLine("Error: Template installation failed"); + return installResult; + } +} + +Console.WriteLine(); +Console.WriteLine("Build templates completed successfully!"); +return 0; + +// Helper methods + +static async Task> GetPackageVersionsAsync( + HttpClient client, string packageName, int[] targetFrameworks) +{ + var result = new Dictionary(); + foreach (int tf in targetFrameworks) + { + result[tf] = null; + } + + string nugetUrl = $"https://azuresearch-usnc.nuget.org/query?q={Uri.EscapeDataString(packageName)}&prerelease=false"; + string json = await client.GetStringAsync(nugetUrl); + using var doc = JsonDocument.Parse(json); + + if (!doc.RootElement.TryGetProperty("data", out var data) || data.GetArrayLength() == 0) + { + return result; + } + + // Find the package with exact ID match + JsonElement? packageData = null; + foreach (var item in data.EnumerateArray()) + { + if (item.TryGetProperty("id", out var idProp) && + string.Equals(idProp.GetString(), packageName, StringComparison.OrdinalIgnoreCase)) + { + packageData = item; + break; + } + } + + if (packageData is null) + { + return result; + } + + if (!packageData.Value.TryGetProperty("versions", out var versions)) + { + return result; + } + + foreach (int tf in targetFrameworks) + { + // Find latest version starting with the target framework number + string? latestVersion = null; + Version? latestParsedVersion = null; + + foreach (var versionItem in versions.EnumerateArray()) + { + if (!versionItem.TryGetProperty("version", out var versionProp)) + { + continue; + } + + string? versionStr = versionProp.GetString(); + if (versionStr is null || !versionStr.StartsWith($"{tf}.")) + { + continue; + } + + // Parse version for comparison (strip prerelease suffix) + string versionToParse = versionStr.Split('-')[0]; + if (Version.TryParse(versionToParse, out var parsedVersion)) + { + if (latestParsedVersion is null || parsedVersion > latestParsedVersion) + { + latestParsedVersion = parsedVersion; + latestVersion = versionStr; + } + } + } + + if (latestVersion is not null) + { + result[tf] = latestVersion; + } + else + { + Console.WriteLine($" Warning: No version found for {packageName} supporting net{tf}.0"); + } + } + + return result; +} + +static int RunDotnet(string arguments) +{ + var psi = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = arguments, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using var process = Process.Start(psi); + if (process is null) + { + return 1; + } + + process.OutputDataReceived += (_, e) => + { + if (e.Data is not null) + { + Console.WriteLine(e.Data); + } + }; + process.ErrorDataReceived += (_, e) => + { + if (e.Data is not null) + { + Console.WriteLine(e.Data); + } + }; + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + process.WaitForExit(); + + return process.ExitCode; +} + +static string GetScriptDirectory([CallerFilePath] string? callerFilePath = null) +{ + return Path.GetDirectoryName(callerFilePath!) ?? Environment.CurrentDirectory; +} diff --git a/build-scripts/ConsoleDialog.cs b/build-scripts/ConsoleDialog.cs new file mode 100644 index 000000000..1fca2a591 --- /dev/null +++ b/build-scripts/ConsoleDialog.cs @@ -0,0 +1,276 @@ +#!/usr/bin/env dotnet + +using System.Diagnostics; + +// Manages a console window for displaying log messages during source generation. + +object _consoleLock = new(); +Process? _consoleProcess = null; +string? _consoleTempFile = null; + +string? title = null; +int wait = 3; + +for (int i = 0; i < args.Length; i++) +{ + string arg = args[i]; + + switch (arg) + { + case "-w": + case "--wait": + wait = int.TryParse(args[i + 1], out int parsedWait) ? parsedWait : wait; + i++; + break; + default: + if (title is null) + { + title = arg; + } + else + { + title = $"{title} {arg}"; + } + break; + } +} + +title ??= "GeoBlazor Build"; + +void ShowOrUpdateConsole(string title, string message) +{ + lock (_consoleLock) + { + // Ensure the temp file exists (create if needed) + if (_consoleTempFile is null || !File.Exists(_consoleTempFile)) + { + _consoleTempFile = Path.Combine(Path.GetTempPath(), $"geoblazor_sourcegen_{Guid.NewGuid():N}.log"); + // Create the file immediately so Get-Content -Wait has something to tail + File.WriteAllText(_consoleTempFile, $" {Environment.NewLine}"); + } + + if (!string.IsNullOrWhiteSpace(message)) + { + // Append message to the temp file + string timestamp = DateTime.Now.ToString("HH:mm:ss"); + string logLine = $"[{timestamp}] {title}: {message}{Environment.NewLine}"; + File.AppendAllText(_consoleTempFile, logLine); + } + + // Start the console window if not already running + if (_consoleProcess is null || _consoleProcess.HasExited) + { + StartConsoleWindow(title); + } + } +} + +void StartConsoleWindow(string title) +{ + try + { + if (OperatingSystem.IsWindows()) + { + StartWindowsConsole(title); + } + else if (OperatingSystem.IsMacOS()) + { + StartMacConsole(); + } + else if (OperatingSystem.IsLinux()) + { + StartLinuxConsole(); + } + } + catch + { + // Console window creation failed - continue silently + // Messages are still written to the temp file and MSBuild diagnostics + } +} + +void StartWindowsConsole(string title) +{ + string escapedPath = _consoleTempFile!.Replace("'", "''"); + string windowTitle = string.IsNullOrWhiteSpace(title) ? "GeoBlazor Build" : title; + string command = $"$Host.UI.RawUI.WindowTitle = '{windowTitle}'; " + + $"Write-Host 'GeoBlazor Source Generator Output' -ForegroundColor Cyan; " + + $"Write-Host ('=' * 50); " + + $"Get-Content -Path '{escapedPath}' -Wait -Tail 100"; + + _consoleProcess = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "pwsh", + Arguments = $"-NoProfile -NoLogo -Command \"{command}\"", + UseShellExecute = true, + CreateNoWindow = false + } + }; + _consoleProcess.Start(); +} + +void StartMacConsole() +{ + string escapedPath = _consoleTempFile!.Replace("'", "'\\''"); + + // Use osascript to open Terminal.app with a pwsh command + string pwshCommand = "pwsh -NoProfile -NoLogo -Command \\\"" + + "Write-Host 'GeoBlazor Source Generator Output' -ForegroundColor Cyan; " + + "Write-Host ('=' * 50); " + + $"Get-Content -Path '{escapedPath}' -Wait -Tail 100\\\""; + + string appleScript = $"tell application \\\"Terminal\\\" to do script \\\"{pwshCommand}\\\""; + + _consoleProcess = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "osascript", + Arguments = $"-e \"{appleScript}\"", + UseShellExecute = false, + CreateNoWindow = true + } + }; + _consoleProcess.Start(); +} + +void StartLinuxConsole() +{ + string escapedPath = _consoleTempFile!.Replace("'", "'\\''"); + string pwshCommand = "pwsh -NoProfile -NoLogo -Command \\\"" + + "Write-Host 'GeoBlazor Source Generator Output' -ForegroundColor Cyan; " + + "Write-Host ('=' * 50); " + + $"Get-Content -Path '{escapedPath}' -Wait -Tail 100\\\""; + + // Try common Linux terminal emulators in order of popularity + string[] terminals = ["gnome-terminal", "konsole", "xfce4-terminal", "xterm"]; + + foreach (string terminal in terminals) + { + try + { + string args = terminal switch + { + "gnome-terminal" => $"-- bash -c \"{pwshCommand}\"", + "konsole" => $"-e bash -c \"{pwshCommand}\"", + "xfce4-terminal" => $"-e \"bash -c \\\"{pwshCommand}\\\"\"", + "xterm" => $"-e bash -c \"{pwshCommand}\"", + _ => $"-e bash -c \"{pwshCommand}\"" + }; + + _consoleProcess = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = terminal, + Arguments = args, + UseShellExecute = false, + CreateNoWindow = true + } + }; + _consoleProcess.Start(); + return; // Success, exit the loop + } + catch + { + // This terminal emulator not available, try the next one + } + } + + // No terminal emulator found - messages still go to temp file and diagnostics +} + +void CloseConsole(string title, int wait) +{ + lock (_consoleLock) + { + try + { + if (_consoleProcess is { HasExited: false } && _consoleTempFile is not null) + { + File.WriteAllText(_consoleTempFile, $"[{DateTime.Now:HH:mm:ss}] {title}: Console closing..."); + // Give a brief moment for final messages to appear + Thread.Sleep(wait * 1000); + _consoleProcess.Kill(); + _consoleProcess.Dispose(); + } + } + catch + { + // Process may have already exited + } + finally + { + _consoleProcess = null; + } + + // Clean up temp file + try + { + if (_consoleTempFile is not null && File.Exists(_consoleTempFile)) + { + File.Delete(_consoleTempFile); + } + } + catch + { + // File may be locked - ignore + } + finally + { + _consoleTempFile = null; + } + } +} + +ShowOrUpdateConsole(title, string.Empty); + +CancellationTokenSource cts = new(); +cts.CancelAfter(TimeSpan.FromSeconds(60)); + +bool hold = false; + +_ = Task.Run(async () => +{ + while ((!cts.IsCancellationRequested || hold) + && (_consoleProcess is null || !_consoleProcess.HasExited)) + { + await Task.Delay(1000); + } + Console.WriteLine("Console dialog timed out. Closing..."); + CloseConsole(title, wait); + Environment.Exit(0); +}); + +while (!cts.IsCancellationRequested) +{ + if (_consoleProcess?.HasExited == true) + { + break; + } + + if (Console.ReadLine() is not { } inputLine) + { + continue; + } + + if (inputLine.Trim().Equals("hold", StringComparison.OrdinalIgnoreCase)) + { + hold = true; + + break; + } + + if (inputLine.Trim().Equals("exit", StringComparison.OrdinalIgnoreCase)) + { + CloseConsole(title, wait); + + break; + } + + ShowOrUpdateConsole(title, inputLine); +} + +Environment.Exit(0); \ No newline at end of file diff --git a/build-scripts/ESBuild.cs b/build-scripts/ESBuild.cs new file mode 100644 index 000000000..447e458b8 --- /dev/null +++ b/build-scripts/ESBuild.cs @@ -0,0 +1,639 @@ +#!/usr/bin/env dotnet + +// ESBuild TypeScript -> JavaScript Compilation Script +// C# file-based app version of esBuild.ps1 +// Usage: dotnet esBuild.cs [options] +// -c, --configuration Build configuration (default: Debug) +// -f, --force Force rebuild, ignoring lock files and record +// -p, --pro Run the GeoBlazor Pro ESBuild process +// -d, --dialog Show a console dialog during build +// -v, --verbose Enable verbose logging +// -h, --help Display help message + +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text.Json; + +// Get the actual script location using CallerFilePath (resolved at compile time) +string scriptDir = GetScriptsDirectory(); +string toolsDir = Path.GetFullPath(Path.Combine(scriptDir, "..", "build-tools")); + +// Parse command line arguments +string configuration = "Debug"; +bool force = false; +bool help = false; +bool pro = false; +bool dialog = false; +bool verbose = false; + +for (int i = 0; i < args.Length; i++) +{ + string arg = args[i].ToLowerInvariant(); + switch (arg) + { + case "-c": + case "--configuration": + if (i + 1 < args.Length) + { + configuration = args[++i]; + } + break; + case "-d": + case "--dialog": + dialog = true; + break; + case "-f": + case "--force": + force = true; + break; + case "-p": + case "--pro": + pro = true; + break; + case "-h": + case "--help": + help = true; + break; + case "-v": + case "--verbose": + verbose = true; + break; + default: + // Check for combined forms like "-c=Release" + if (arg.StartsWith("-c=") || arg.StartsWith("--configuration=")) + { + configuration = args[i].Split('=', 2)[1]; + } + break; + } +} + +if (help) +{ + Trace.WriteLine("ESBuild TypeScript -> JavaScript Compilation Script"); + Trace.WriteLine(""); + Trace.WriteLine("Parameters:"); + Trace.WriteLine(" -f, --force Removes any lock files and forces the script to run"); + Trace.WriteLine(" -c, --configuration Build configuration (default is 'Debug')"); + Trace.WriteLine(" Valid values are 'Debug' and 'Release'"); + Trace.WriteLine(" -p, --pro Run the GeoBlazor Pro ESBuild process"); + Trace.WriteLine(" -h, --help Display this help message"); + return 0; +} + +Trace.Listeners.Add(new ConsoleTraceListener()); + +Process? dialogProcess = null; + +if (verbose) +{ + if (dialog) // only start the dialog early if we are in Verbose + Dialog mode + { + dialogProcess = StartConsoleDialog(toolsDir, $"GeoBlazor {(pro ? "Pro" : "Core")} ESBuild"); + } + Trace.WriteLine("Launching ESBuild..."); +} + +// Normalize configuration +configuration = configuration.Equals("release", StringComparison.OrdinalIgnoreCase) ? "Release" : "Debug"; + +// The build folder is where this script is located (scriptDir set at top using CallerFilePath) +// Core source is at ../src/dymaptic.GeoBlazor.Core relative to build folder +string coreSourceDir = Path.GetFullPath(Path.Combine(scriptDir, "..", "src", "dymaptic.GeoBlazor.Core")); +string proSourceDir = Path.GetFullPath(Path.Combine(scriptDir, "..", "..", "src", "dymaptic.GeoBlazor.Pro")); +string sourceDir = pro ? proSourceDir : coreSourceDir; + +string coreScriptsDir = Path.Combine(coreSourceDir, "Scripts"); +string proScriptsDir = Path.Combine(proSourceDir, "Scripts"); +string scriptsDir = pro ? proScriptsDir : coreScriptsDir; + +string coreOutputDir = Path.Combine(coreSourceDir, "wwwroot", "js"); +string proOutputDir = Path.Combine(proSourceDir, "wwwroot", "js"); +string outputDir = pro ? proOutputDir : coreOutputDir; + +string coreRecordFilePath = Path.GetFullPath(Path.Combine(coreSourceDir, "..", ".esbuild-record.json")); +string proRecordFilePath = Path.GetFullPath(Path.Combine(proSourceDir, "..", ".esbuild-record.json")); +string recordFilePath = pro ? proRecordFilePath : coreRecordFilePath; + +string coreDebugLockFile = Path.Combine(coreSourceDir, "esBuild.Debug.lock"); +string coreReleaseLockFile = Path.Combine(coreSourceDir, "esBuild.Release.lock"); +string proDebugLockFile = Path.Combine(proSourceDir, "esBuild.Debug.lock"); +string proReleaseLockFile = Path.Combine(proSourceDir, "esBuild.Release.lock"); +string debugLockFile = configuration == "Debug" ? (pro ? proDebugLockFile : coreDebugLockFile) : string.Empty; +string releaseLockFile = configuration == "Release" ? (pro ? proReleaseLockFile : coreReleaseLockFile) : string.Empty; +string coreLockFilePath = configuration == "Release" ? coreReleaseLockFile : coreDebugLockFile; +string proLockFilePath = configuration == "Release" ? proReleaseLockFile : proDebugLockFile; +string lockFilePath = pro ? proLockFilePath : coreLockFilePath; + +// Handle --force flag: delete record file +if (force && File.Exists(coreRecordFilePath)) +{ + Trace.WriteLine("Force rebuild: Deleting existing record file."); + File.Delete(coreRecordFilePath); +} +if (force && File.Exists(proRecordFilePath)) +{ + Trace.WriteLine("Force rebuild: Deleting existing record file."); + File.Delete(proRecordFilePath); +} + +string currentBranch = GetCurrentGitBranch(coreSourceDir); + +bool needsBuild = CheckIfNeedsBuild( + coreRecordFilePath, + currentBranch, + coreScriptsDir, + coreOutputDir); + +if (pro) +{ + currentBranch = GetCurrentGitBranch(proSourceDir); + needsBuild = CheckIfNeedsBuild( + proRecordFilePath, + currentBranch, + proScriptsDir, + proOutputDir); +} + +if (!needsBuild) +{ + KillDialog(dialogProcess); + Environment.Exit(0); +} + +if (!verbose) +{ + if (dialog) // start the dialog now if we are not in Verbose mode + { + dialogProcess = StartConsoleDialog(toolsDir, $"GeoBlazor {(pro ? "Pro" : "Core")} ESBuild"); + } + Trace.WriteLine("Launching ESBuild..."); +} + +// Check if the process is locked for the current configuration +bool locked = configuration == "Debug" && File.Exists(debugLockFile) + || configuration == "Release" && File.Exists(releaseLockFile); + +// Prevent multiple instances of the script from running at the same time +if (locked) +{ + if (force) + { + if (File.Exists(debugLockFile)) File.Delete(debugLockFile); + if (File.Exists(releaseLockFile)) File.Delete(releaseLockFile); + Trace.WriteLine("Cleared esBuild lock files"); + } + else + { + Trace.WriteLine("Another instance of the script is already running. Exiting."); + KillDialog(dialogProcess); + return 1; + } +} + +try +{ + // Lock + File.WriteAllText(lockFilePath, DateTime.UtcNow.ToString("o")); + + // Ensure output directory exists + if (!Directory.Exists(outputDir)) + { + Directory.CreateDirectory(outputDir); + } + + if (pro) + { + // Copy core Scripts to Pro Scripts + CopyScriptsToPro(coreScriptsDir, proScriptsDir, verbose); + } + + // npm install + var (installOutput, installExitCode) = RunNpmCommand(sourceDir, "install", dialogProcess); + Trace.WriteLine("-----"); + + if (installExitCode != 0 || HasErrorOrWarning(installOutput)) + { + Trace.WriteLine("NPM Install failed"); + HoldDialog(dialogProcess); + return 1; + } + + // Run ESLint before build + Trace.WriteLine("Running ESLint..."); + var (lintOutput, lintExitCode) = RunNpmCommand(sourceDir, "run lint", dialogProcess); + Trace.WriteLine("-----"); + + if (lintExitCode != 0 || lintOutput.Any(l => l.Contains("error", StringComparison.OrdinalIgnoreCase))) + { + Trace.WriteLine("ESLint found errors"); + HoldDialog(dialogProcess); + return 1; + } + + // Run build + string buildCommand = configuration == "Release" ? "run releaseBuild" : "run debugBuild"; + var (buildOutput, buildExitCode) = RunNpmCommand(sourceDir, buildCommand, dialogProcess); + Trace.WriteLine("-----"); + + if (buildExitCode != 0 || HasErrorOrWarning(buildOutput)) + { + HoldDialog(dialogProcess); + return 1; + } + + // Update build record on success + SaveBuildRecord(recordFilePath, currentBranch); + + Trace.WriteLine("NPM Build Complete"); + KillDialog(dialogProcess); + return 0; +} +catch (Exception ex) +{ + Trace.WriteLine("An error occurred in esBuild.cs"); + Trace.WriteLine($"{ex.Message}{Environment.NewLine}{ex.StackTrace}"); + HoldDialog(dialogProcess); + return 1; +} +finally +{ + // Unlock + if (File.Exists(lockFilePath)) + { + File.Delete(lockFilePath); + } +} + +// Helper methods + +static string GetCurrentGitBranch(string workingDirectory) +{ + try + { + var psi = new ProcessStartInfo + { + FileName = "git", + Arguments = "rev-parse --abbrev-ref HEAD", + WorkingDirectory = workingDirectory, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = Process.Start(psi); + if (process is null) return "unknown"; + + string output = process.StandardOutput.ReadToEnd().Trim(); + process.WaitForExit(); + + return process.ExitCode == 0 ? output : "unknown"; + } + catch + { + return "unknown"; + } +} + +static (long Timestamp, string Branch) GetLastBuildRecord(string recordFilePath) +{ + if (!File.Exists(recordFilePath)) + { + return (0, "unknown"); + } + + try + { + string json = File.ReadAllText(recordFilePath); + using var doc = JsonDocument.Parse(json); + var root = doc.RootElement; + + long timestamp = root.TryGetProperty("timestamp", out var ts) ? ts.GetInt64() : 0; + string branch = root.TryGetProperty("branch", out var br) ? br.GetString() ?? "unknown" : "unknown"; + + return (timestamp, branch); + } + catch + { + return (0, "unknown"); + } +} + +static bool CheckIfNeedsBuild(string recordFilePath, string currentBranch, string scriptsDir, string outputDir) +{ + // Check if build is needed + var lastBuild = GetLastBuildRecord(recordFilePath); + bool branchChanged = currentBranch != lastBuild.Branch; + + if (branchChanged) + { + Trace.WriteLine($"Git branch changed from \"{lastBuild.Branch}\" to \"{currentBranch}\". Rebuilding..."); + return true; + } + + if (!GetScriptsModifiedSince(scriptsDir, lastBuild.Timestamp)) + { + Trace.WriteLine("No changes in Scripts folder since last build."); + + // Check output directory for existing files + if (Directory.Exists(outputDir) && Directory.GetFiles(outputDir).Length > 0) + { + Trace.WriteLine("Output directory is not empty. Skipping build."); + return false; + } + else + { + Trace.WriteLine("Output directory is empty. Proceeding with build."); + return true; + } + } + + Trace.WriteLine("Changes detected in Scripts folder. Proceeding with build."); + return true; +} + +static void CopyScriptsToPro(string coreScriptsDir, string proScriptsDir, bool verbose) +{ + Trace.WriteLine("Copying core Scripts to Pro Scripts directory..."); + if (!Directory.Exists(proScriptsDir)) + { + Directory.CreateDirectory(proScriptsDir); + } + + int copiedCount = 0; + int skippedCount = 0; + List fileNames = []; + + foreach (string filePath in Directory.GetFiles(coreScriptsDir, "*.ts", SearchOption.AllDirectories)) + { + string fileName = Path.GetFileName(filePath); + fileNames.Add(fileName); + string destinationPath = Path.Combine(proScriptsDir, fileName); + + if (!File.Exists(destinationPath)) + { + File.Copy(filePath, destinationPath, true); + copiedCount++; + if (verbose) + { + Trace.WriteLine($"Copied new file: {fileName}"); + } + continue; + } + + DateTime sourceDate = File.GetLastWriteTimeUtc(filePath); + DateTime destDate = File.GetLastWriteTimeUtc(destinationPath); + + if (sourceDate > destDate) + { + File.Copy(filePath, destinationPath, true); + if (verbose) + { + Trace.WriteLine($"Updated file: {fileName}"); + } + copiedCount++; + } + else + { + skippedCount++; + } + } + + Trace.WriteLine($"Copied {copiedCount} files, skipped {skippedCount} files."); +} + +static void SaveBuildRecord(string recordFilePath, string branch) +{ + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + // Write JSON manually to avoid reflection-based serialization (not compatible with Native AOT) + string json = $$""" + { + "timestamp": {{timestamp}}, + "branch": "{{branch.Replace("\\", "\\\\").Replace("\"", "\\\"")}}" + } + """; + File.WriteAllText(recordFilePath, json); +} + +static bool GetScriptsModifiedSince(string scriptsDir, long lastTimestamp) +{ + if (!Directory.Exists(scriptsDir)) + { + return true; // Force rebuild if Scripts folder doesn't exist + } + + var lastBuildTime = DateTimeOffset.FromUnixTimeMilliseconds(lastTimestamp).DateTime; + + foreach (string file in Directory.GetFiles(scriptsDir, "*", SearchOption.AllDirectories)) + { + if (File.GetLastWriteTime(file) > lastBuildTime) + { + return true; + } + } + + return false; +} + +static Process? StartConsoleDialog(string buildDir, string title) +{ + try + { + string consoleDialogPath = Path.Combine(buildDir, "ConsoleDialog.dll"); + if (!File.Exists(consoleDialogPath)) + { + Trace.WriteLine($"ConsoleDialog.dll not found at {consoleDialogPath}"); + return null; + } + + string[] args = + [ + "ConsoleDialog.dll", + $"\"{title}\"", + "-w", + "2" + ]; + + var psi = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = string.Join(" ", args), + WorkingDirectory = buildDir, + RedirectStandardInput = true, + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + Process? dialog = Process.Start(psi); + + if (dialog?.StandardInput is null) + { + Trace.WriteLine("Failed to start console dialog. Exiting."); + } + else + { + dialog.StandardInput.AutoFlush = true; + Trace.Listeners.Add(new DialogTraceListener(dialog)); + } + + return dialog; + } + catch (Exception ex) + { + Trace.WriteLine($"Failed to start ConsoleDialog: {ex.Message}"); + return null; + } +} + +static void KillDialog(Process? dialog) +{ + if (dialog is null || dialog.HasExited) + { + return; + } + + try + { + if (dialog?.StandardInput is not null) + { + // Flush to ensure all pending messages are sent before exit + dialog.StandardInput.Flush(); + // Small delay to allow the dialog to display the final message + Thread.Sleep(500); + dialog.StandardInput.WriteLine("exit"); + } + } + catch + { + dialog.Kill(); + } +} + +static (List Output, int ExitCode) RunNpmCommand(string workingDirectory, string command, Process? dialogProcess) +{ + var output = new List(); + + try + { + // Use pwsh (PowerShell 7) for cross-platform compatibility + // This ensures proper shell resolution and avoids issues with multiple npm installations + var psi = new ProcessStartInfo + { + FileName = "pwsh", + Arguments = $"-NoProfile -Command \"npm {command}\"", + WorkingDirectory = workingDirectory, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = Process.Start(psi); + if (process is null) + { + return (["Failed to start npm"], 1); + } + + // Read output asynchronously + process.OutputDataReceived += (sender, e) => + { + if (e.Data is not null) + { + Trace.WriteLine(e.Data); + output.Add(e.Data); + } + }; + + process.ErrorDataReceived += (sender, e) => + { + if (e.Data is not null) + { + Trace.WriteLine(e.Data); + output.Add(e.Data); + } + }; + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + process.WaitForExit(); + + return (output, process.ExitCode); + } + catch (Exception ex) + { + Trace.WriteLine($"Error running npm {command}: {ex.Message}{Environment.NewLine}{ex.StackTrace}"); + output.Add(ex.Message); + return (output, 1); + } +} + +static bool HasErrorOrWarning(List output) +{ + return output.Any(line => + line.Contains("Error", StringComparison.OrdinalIgnoreCase) || + line.Contains("Warning", StringComparison.OrdinalIgnoreCase)); +} + +static string GetScriptsDirectory([CallerFilePath] string? callerFilePath = null) +{ + // When running as a pre-compiled DLL, [CallerFilePath] contains the compile-time path + // which is invalid at runtime (especially in Docker containers). + // Detect this by checking if the file exists at the caller path. + if (!string.IsNullOrEmpty(callerFilePath) && File.Exists(callerFilePath)) + { + return Path.GetDirectoryName(callerFilePath)!; + } + + // Running as a DLL - use AppContext.BaseDirectory which points to the DLL location + // The DLL is in build-tools/, and scripts are in build-scripts/ (sibling directory) + string dllDirectory = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + return Path.Combine(Path.GetDirectoryName(dllDirectory)!, "build-scripts"); +} + +static void HoldDialog(Process? dialog) +{ + if (dialog?.StandardInput is not null && !dialog.HasExited) + { + dialog.StandardInput.WriteLine("NPM Install failed"); + } +} + +public class DialogTraceListener(Process dialog) : TraceListener +{ + public override void Write(string? message) + { + if (dialog.StandardInput is null || dialog.HasExited) + { + return; + } + + try + { + dialog.StandardInput.Write(message); + } + catch + { + // Dialog may have closed - ignore + } + } + + public override void WriteLine(string? message) + { + if (dialog.StandardInput is null || dialog.HasExited) + { + return; + } + + try + { + dialog.StandardInput.WriteLine(message); + } + catch + { + // Dialog may have closed - ignore + } + } +} \ No newline at end of file diff --git a/build-scripts/ESBuildClearLocks.cs b/build-scripts/ESBuildClearLocks.cs new file mode 100644 index 000000000..948db0b4b --- /dev/null +++ b/build-scripts/ESBuildClearLocks.cs @@ -0,0 +1,72 @@ +#!/usr/bin/env dotnet + +// ESBuild Clear Locks Script +// C# file-based app version of esBuildClearLocks.ps1 +// Removes ESBuild lock files for both Core and Pro projects +// +// Usage: dotnet ESBuildClearLocks.cs +// -h, --help Display help message + +using System.Runtime.CompilerServices; + +// Parse command line arguments +bool help = args.Any(a => a is "-h" or "--help"); + +if (help) +{ + Console.WriteLine("ESBuild Clear Locks Script"); + Console.WriteLine("Removes ESBuild lock files for both Core and Pro projects."); + Console.WriteLine(); + Console.WriteLine("Usage: dotnet ESBuildClearLocks.cs"); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine(" -h, --help Display this help message"); + return 0; +} + +string scriptDir = GetScriptDirectory(); + +// Define lock file paths relative to script location (build-scripts folder) +string[] lockFiles = +[ + Path.GetFullPath(Path.Combine(scriptDir, "..", "src", "dymaptic.GeoBlazor.Core", "esBuild.Debug.lock")), + Path.GetFullPath(Path.Combine(scriptDir, "..", "src", "dymaptic.GeoBlazor.Core", "esBuild.Release.lock")), + Path.GetFullPath(Path.Combine(scriptDir, "..", "..", "src", "dymaptic.GeoBlazor.Pro", "esBuild.Debug.lock")), + Path.GetFullPath(Path.Combine(scriptDir, "..", "..", "src", "dymaptic.GeoBlazor.Pro", "esBuild.Release.lock")) +]; + +int deletedCount = 0; + +foreach (string lockFile in lockFiles) +{ + if (File.Exists(lockFile)) + { + try + { + File.Delete(lockFile); + Console.WriteLine($"Deleted: {lockFile}"); + deletedCount++; + } + catch (Exception ex) + { + Console.WriteLine($"Failed to delete {lockFile}: {ex.Message}"); + } + } +} + +if (deletedCount > 0) +{ + Console.WriteLine($"Cleared {deletedCount} esBuild lock file(s)"); +} +else +{ + Console.WriteLine("No esBuild lock files found"); +} + +return 0; + +// Helper method +static string GetScriptDirectory([CallerFilePath] string? callerFilePath = null) +{ + return Path.GetDirectoryName(callerFilePath!) ?? Environment.CurrentDirectory; +} diff --git a/build-scripts/ESBuildWaitForCompletion.cs b/build-scripts/ESBuildWaitForCompletion.cs new file mode 100644 index 000000000..a76c72a29 --- /dev/null +++ b/build-scripts/ESBuildWaitForCompletion.cs @@ -0,0 +1,149 @@ +#!/usr/bin/env dotnet + +// ESBuild Wait For Completion Script +// C# file-based app version of esBuildWaitForCompletion.ps1 +// Waits for ESBuild lock files to be released before proceeding +// +// Usage: dotnet ESBuildWaitForCompletion.cs [options] +// -c, --configuration Build configuration (default: Debug) +// -t, --timeout Timeout in seconds (default: 30) +// -h, --help Display help message +// +// Example: +// dotnet ESBuildWaitForCompletion.cs +// dotnet ESBuildWaitForCompletion.cs -c Release +// dotnet ESBuildWaitForCompletion.cs -c Debug -t 60 + +using System.Runtime.CompilerServices; + +// Parse command line arguments +string configuration = "Debug"; +int timeout = 30; +bool help = false; + +for (int i = 0; i < args.Length; i++) +{ + string arg = args[i].ToLowerInvariant(); + switch (arg) + { + case "-c": + case "--configuration": + if (i + 1 < args.Length) + { + configuration = args[++i]; + } + break; + case "-t": + case "--timeout": + if (i + 1 < args.Length && int.TryParse(args[++i], out int t)) + { + timeout = t; + } + break; + case "-h": + case "--help": + help = true; + break; + } +} + +if (help) +{ + Console.WriteLine("ESBuild Wait For Completion Script"); + Console.WriteLine("Waits for ESBuild lock files to be released before proceeding."); + Console.WriteLine(); + Console.WriteLine("Usage: dotnet ESBuildWaitForCompletion.cs [options]"); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine(" -c, --configuration Build configuration (default: Debug)"); + Console.WriteLine(" -t, --timeout Timeout in seconds (default: 30)"); + Console.WriteLine(" -h, --help Display this help message"); + Console.WriteLine(); + Console.WriteLine("Examples:"); + Console.WriteLine(" dotnet ESBuildWaitForCompletion.cs"); + Console.WriteLine(" dotnet ESBuildWaitForCompletion.cs -c Release"); + Console.WriteLine(" dotnet ESBuildWaitForCompletion.cs -c Debug -t 60"); + return 0; +} + +// Normalize configuration +configuration = configuration.Equals("release", StringComparison.OrdinalIgnoreCase) ? "Release" : "Debug"; + +string scriptDir = GetScriptDirectory(); + +// Define paths relative to script location (build-scripts folder) +// Core: ../src/dymaptic.GeoBlazor.Core +// Pro: ../../src/dymaptic.GeoBlazor.Pro +string coreRootDir = Path.GetFullPath(Path.Combine(scriptDir, "..", "src", "dymaptic.GeoBlazor.Core")); +string proRootDir = Path.GetFullPath(Path.Combine(scriptDir, "..", "..", "src", "dymaptic.GeoBlazor.Pro")); + +string coreLockFilePath = Path.Combine(coreRootDir, $"esBuild.{configuration}.lock"); +string proLockFilePath = Path.Combine(proRootDir, $"esBuild.{configuration}.lock"); + +Console.WriteLine($"Waiting for lock files: {coreLockFilePath}, {proLockFilePath}"); + +bool coreLockExists = File.Exists(coreLockFilePath); +bool proLockExists = File.Exists(proLockFilePath); + +if (coreLockExists || proLockExists) +{ + Console.WriteLine("Lock file found. Waiting for release."); +} +else +{ + Console.WriteLine("No lock file found. Exiting."); + return 0; +} + +int elapsed = 0; + +while (File.Exists(coreLockFilePath) || File.Exists(proLockFilePath)) +{ + Thread.Sleep(1000); + Console.Write("."); + elapsed++; + + if (elapsed >= timeout) + { + Console.WriteLine(); + Console.WriteLine($"Timeout reached ({timeout} seconds). Deleting lock files."); + + if (File.Exists(coreLockFilePath)) + { + try + { + File.Delete(coreLockFilePath); + Console.WriteLine($"Deleted: {coreLockFilePath}"); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to delete {coreLockFilePath}: {ex.Message}"); + } + } + + if (File.Exists(proLockFilePath)) + { + try + { + File.Delete(proLockFilePath); + Console.WriteLine($"Deleted: {proLockFilePath}"); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to delete {proLockFilePath}: {ex.Message}"); + } + } + + break; + } +} + +Console.WriteLine(); +Console.WriteLine("Lock file removed. Exiting."); +return 0; + +// Helper method +static string GetScriptDirectory([CallerFilePath] string? callerFilePath = null) +{ + return Path.GetDirectoryName(callerFilePath!) ?? Environment.CurrentDirectory; +} diff --git a/build-scripts/FetchNuGetVersion.cs b/build-scripts/FetchNuGetVersion.cs new file mode 100644 index 000000000..60f40f061 --- /dev/null +++ b/build-scripts/FetchNuGetVersion.cs @@ -0,0 +1,98 @@ +#!/usr/bin/env dotnet + +// Fetch NuGet Package Version Script +// C# file-based app version of fetchNuGetVersion.ps1 +// Usage: dotnet FetchNuGetVersion.cs +// Example: dotnet FetchNuGetVersion.cs dymaptic.GeoBlazor.Core +// Returns the latest non-prerelease version of the specified package from NuGet.org + +using System.Text.Json; + +if (args.Length == 0 || string.IsNullOrWhiteSpace(args[0])) +{ + Console.Error.WriteLine("Package name must be provided."); + return 1; +} + +string package = args[0]; + +try +{ + // Query NuGet API (same endpoint as the PowerShell script) + string nugetUrl = $"https://azuresearch-usnc.nuget.org/query?q={Uri.EscapeDataString(package)}&prerelease=false"; + + using var client = new HttpClient(); + client.DefaultRequestHeaders.Add("User-Agent", "GeoBlazor-Build-Script"); + + string json = await client.GetStringAsync(nugetUrl); + using var doc = JsonDocument.Parse(json); + var root = doc.RootElement; + + if (!root.TryGetProperty("data", out var data) || data.GetArrayLength() == 0) + { + Console.Error.WriteLine("Could not determine latest version from NuGet API."); + return 1; + } + + // Find the package with exact match and get the highest version + string? latestVersion = null; + + foreach (var item in data.EnumerateArray()) + { + // Check for exact package name match (case-insensitive) + if (item.TryGetProperty("id", out var idProp)) + { + string? id = idProp.GetString(); + if (string.Equals(id, package, StringComparison.OrdinalIgnoreCase)) + { + if (item.TryGetProperty("version", out var versionProp)) + { + latestVersion = versionProp.GetString(); + break; // NuGet API returns the latest version for each package + } + } + } + } + + // Fallback: if no exact match, try to parse versions from all results + if (latestVersion is null) + { + var versions = new List<(Version Version, string Original)>(); + + foreach (var item in data.EnumerateArray()) + { + if (item.TryGetProperty("version", out var versionProp)) + { + string? versionStr = versionProp.GetString(); + if (versionStr is not null && Version.TryParse(versionStr.Split('-')[0], out var version)) + { + versions.Add((version, versionStr)); + } + } + } + + if (versions.Count > 0) + { + latestVersion = versions.OrderByDescending(v => v.Version).First().Original; + } + } + + if (latestVersion is null) + { + Console.Error.WriteLine("Could not determine latest version from NuGet API."); + return 1; + } + + Console.WriteLine(latestVersion); + return 0; +} +catch (HttpRequestException ex) +{ + Console.Error.WriteLine($"Failed to query NuGet API: {ex.Message}"); + return 1; +} +catch (JsonException ex) +{ + Console.Error.WriteLine($"Failed to parse NuGet API response: {ex.Message}"); + return 1; +} diff --git a/build-scripts/GeoBlazorBuild.cs b/build-scripts/GeoBlazorBuild.cs new file mode 100644 index 000000000..8ed261266 --- /dev/null +++ b/build-scripts/GeoBlazorBuild.cs @@ -0,0 +1,1074 @@ +#!/usr/bin/env dotnet + +// GeoBlazorBuild - Primary build script for GeoBlazor and GeoBlazor Pro +// Usage: dotnet GeoBlazorBuild.dll [options] or ./GeoBlazorBuild.exe [options] +// -pro Build GeoBlazor Pro as well as Core (default is false) +// -pub, --publish-version Truncate the build version to 3 digits for NuGet (default is false) +// -obf, --obfuscate Obfuscate the Pro license validation logic (default is false) +// -docs, --generate-docs Generate documentation files for the docs site (default is false) +// -xml, --generate-xml Generate the XML comments for intellisense (default is false) +// -pkg, --package Create NuGet packages (default is false) +// -bl, --binlog Generate MSBuild binary log files (default is false) +// -v, --version Specify a custom version number, or "current" (default is to auto-increment) +// -c, --configuration Build configuration (default is 'Release') +// -vc, --validator-config Validator build configuration (default is 'Release') +// -su, --server-url License server URL (default is 'https://licensing.dymaptic.com') +// -retries Number of times to retry the build on failure (default is 5) +// -h, --help Display this help message + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Xml.Linq; + +// Paths +// Get the script cs file path, that way we can run this script from either the CS file or the Executable +string scriptsDir = GetScriptsDirectory(); +string toolsDir = Path.GetFullPath(Path.Combine(scriptsDir, "..", "build-tools")); +// Build folder is at GeoBlazor/build/, Core root is GeoBlazor/ +string coreRepoRoot = Path.GetFullPath(Path.Combine(toolsDir, "..")); +string proRepoRoot = Path.GetFullPath(Path.Combine(coreRepoRoot, "..")); + +string corePropsPath = Path.Combine(coreRepoRoot, "Directory.Build.props"); +string coreProjectPath = Path.Combine(coreRepoRoot, "src", "dymaptic.GeoBlazor.Core"); +string proPropsPath = Path.Combine(proRepoRoot, "Directory.Build.props"); +string proProjectPath = Path.Combine(proRepoRoot, "src", "dymaptic.GeoBlazor.Pro"); +string validatorProjectPath = Path.Combine(proRepoRoot, "src", "dymaptic.GeoBlazor.Pro.Validator"); + +DateTime scriptStartTime = DateTime.Now; + +// Parse command line arguments +bool pro = false; +bool publishVersion = false; +bool obfuscate = false; +bool generateDocs = false; +bool generateXmlComments = false; +bool package = false; +bool binlog = false; +bool help = false; +string? customVersion = null; +string configuration = "Release"; +string validatorConfig = "Release"; +string serverUrl = "https://licensing.dymaptic.com"; +int buildRetries = 5; + +for (int i = 0; i < args.Length; i++) +{ + string arg = args[i].ToLowerInvariant(); + switch (arg) + { + case "-pro": + case "--pro": + pro = true; + break; + case "-pub": + case "--publish-version": + publishVersion = true; + break; + case "-obf": + case "--obfuscate": + obfuscate = true; + break; + case "-docs": + case "--generate-docs": + generateDocs = true; + break; + case "-xml": + case "--generate-xml": + generateXmlComments = true; + break; + case "-pkg": + case "--package": + package = true; + break; + case "-bl": + case "--binlog": + binlog = true; + break; + case "-h": + case "--help": + help = true; + break; + case "-v": + case "--version": + if (i + 1 < args.Length) + { + string version = args[++i]; + if (version.Trim('"') == "current") + { + XDocument coreProps = XDocument.Load(corePropsPath); + string? currentCoreVersion = coreProps.Root?.Element("PropertyGroup")?.Element("CoreVersion")?.Value; + customVersion = currentCoreVersion; + } + else + { + customVersion = version.Trim('"'); + } + } + break; + case "-c": + case "--configuration": + if (i + 1 < args.Length) + { + configuration = args[++i]; + } + break; + case "-vc": + case "--validator-config": + if (i + 1 < args.Length) + { + validatorConfig = args[++i]; + } + break; + case "-su": + case "--server-url": + if (i + 1 < args.Length) + { + serverUrl = args[++i]; + } + break; + case "-retries": + case "--retries": + if (i + 1 < args.Length && int.TryParse(args[++i], out int retries)) + { + buildRetries = retries; + } + break; + } +} + +if (help) +{ + Console.WriteLine("GeoBlazor Build Script"); + Console.WriteLine(); + Console.WriteLine("Parameters:"); + Console.WriteLine(" -pro Build GeoBlazor Pro as well as Core (default is false)"); + Console.WriteLine(" -pub, --publish-version Truncate the build version to 3 digits for NuGet (default is false)"); + Console.WriteLine(" -obf, --obfuscate Obfuscate the Pro license validation logic (default is false)"); + Console.WriteLine(" -docs, --generate-docs Generate documentation files for the docs site (default is false)"); + Console.WriteLine(" -xml, --generate-xml Generate the XML comments for intellisense (default is false)"); + Console.WriteLine(" -pkg, --package Create NuGet packages (default is false)"); + Console.WriteLine(" -bl, --binlog Generate MSBuild binary log files (default is false)"); + Console.WriteLine(" -v, --version Specify a custom version number (default is to auto-increment)"); + Console.WriteLine(" -c, --configuration Build configuration (default is 'Release')"); + Console.WriteLine(" -vc, --validator-config Validator build configuration (default is 'Release')"); + Console.WriteLine(" -su, --server-url License server URL (default is 'https://licensing.dymaptic.com')"); + Console.WriteLine(" -retries Number of times to retry the build on failure (default is 5)"); + Console.WriteLine(" -h, --help Display this help message"); + return 0; +} + +// If generating docs, also generate XML comments +if (generateDocs) +{ + generateXmlComments = true; +} + +Console.WriteLine("Starting GeoBlazor Build Script"); +Console.WriteLine($"Pro Build: {pro}"); +Console.WriteLine($"Set NuGet Publish Version Build: {publishVersion}"); +Console.WriteLine($"Obfuscate Pro Build: {obfuscate}"); +Console.WriteLine($"Generate Documentation Files: {generateDocs}"); +Console.WriteLine($"Generate XML Documentation: {generateXmlComments}"); +Console.WriteLine($"Build Package: {package}"); +Console.WriteLine($"Version: {customVersion ?? "(auto)"}"); +Console.WriteLine($"Configuration: {configuration}"); +Console.WriteLine($"Validator Configuration: {validatorConfig}"); +Console.WriteLine($"License Server URL: {serverUrl}"); + +string binlogFlag = binlog ? "-bl" : ""; + +int step = 1; +DateTime stepStartTime = DateTime.Now; + +// Step 1: ensure other build scripts are up to date: +WriteStepHeader(step, "Updating build scripts to latest versions..."); +Console.WriteLine($"Running {scriptsDir}/ScriptBuilder.cs to update build scripts in {toolsDir}"); +await RunDotnetCommandWithOutputAsync(scriptsDir, "run", "ScriptBuilder.cs", "--exclude", "GeoBlazorBuild.cs"); +WriteStepCompleted(step, stepStartTime); +step++; + +string otherConfiguration = configuration.Equals("Release", StringComparison.OrdinalIgnoreCase) ? "Debug" : "Release"; + +// Lock file paths +string coreLockFilePath = Path.Combine(coreProjectPath, $"esBuild.{otherConfiguration}.lock"); +string proLockFilePath = Path.Combine(proProjectPath, $"esProBuild.{otherConfiguration}.lock"); + +// Check for existing locks from other configuration +if (File.Exists(coreLockFilePath) || File.Exists(proLockFilePath)) +{ + Console.WriteLine("Another instance of the esBuild scripts are already running, please wait."); + while (File.Exists(coreLockFilePath) || File.Exists(proLockFilePath)) + { + Thread.Sleep(1000); + Console.Write("."); + if (scriptStartTime.AddMinutes(1) < DateTime.Now) + { + if (File.Exists(coreLockFilePath)) + { + Console.WriteLine($"\nLock file {coreLockFilePath} still exists after 1 minute, removing stale lock."); + File.Delete(coreLockFilePath); + } + if (File.Exists(proLockFilePath)) + { + Console.WriteLine($"\nLock file {proLockFilePath} still exists after 1 minute, removing stale lock."); + File.Delete(proLockFilePath); + } + } + } + Console.WriteLine("Lock released, continuing..."); +} + +// Create lock files for current configuration +string currentCoreLockFilePath = Path.Combine(coreProjectPath, $"esBuild.{configuration}.lock"); +string currentProLockFilePath = Path.Combine(proProjectPath, $"esProBuild.{configuration}.lock"); + +try +{ + // Set environment variable + Environment.SetEnvironmentVariable("PipelineBuild", "true"); + + string version = customVersion ?? ""; + bool customVersionSet = !string.IsNullOrEmpty(customVersion); + + // Step 2: Clean old build artifacts + stepStartTime = DateTime.Now; + WriteStepHeader(step, "Cleaning old build artifacts"); + + await RunDotnetCommand(coreProjectPath, "clean", + $"\"{Path.Combine(coreProjectPath, "dymaptic.GeoBlazor.Core.csproj")}\"", "/p:PipelineBuild=true"); + DeleteDirectoryIfExists(Path.Combine(coreProjectPath, "bin")); + DeleteDirectoryIfExists(Path.Combine(coreProjectPath, "obj")); + DeleteDirectoryContentsIfExists(Path.Combine(coreProjectPath, "wwwroot", "js")); + DeleteDirectoryIfExists(Path.Combine(coreProjectPath, "node_modules"), usePowerShell: true); + + if (pro) + { + await RunDotnetCommand(proProjectPath, "clean", + $"\"{Path.Combine(proProjectPath, "dymaptic.GeoBlazor.Pro.csproj")}\"", "/p:PipelineBuild=true"); + DeleteDirectoryIfExists(Path.Combine(proProjectPath, "bin")); + DeleteDirectoryIfExists(Path.Combine(proProjectPath, "obj")); + DeleteDirectoryContentsIfExists(Path.Combine(proProjectPath, "obf")); + DeleteDirectoryContentsIfExists(Path.Combine(proProjectPath, "build", "resources")); + DeleteDirectoryContentsIfExists(Path.Combine(proProjectPath, "wwwroot", "js")); + DeleteDirectoryIfExists(Path.Combine(proProjectPath, "node_modules"), usePowerShell: true); + + if (Directory.Exists(validatorProjectPath)) + { + await RunDotnetCommand(validatorProjectPath, "clean", + $"\"{Path.Combine(validatorProjectPath, "dymaptic.GeoBlazor.Pro.V.csproj")}\""); + DeleteDirectoryIfExists(Path.Combine(validatorProjectPath, "bin")); + DeleteDirectoryIfExists(Path.Combine(validatorProjectPath, "obj")); + DeleteDirectoryContentsIfExists(Path.Combine(validatorProjectPath, "obf")); + } + } + WriteStepCompleted(step, stepStartTime); + step++; + + // Step 3: Update library versions (if no custom version specified) + if (!customVersionSet) + { + stepStartTime = DateTime.Now; + WriteStepHeader(step, "Updating Library Versions"); + + XDocument coreProps = XDocument.Load(corePropsPath); + string? currentCoreVersion = coreProps.Root?.Element("PropertyGroup")?.Element("CoreVersion")?.Value; + + string newCoreVersion = await BumpVersionAsync(coreRepoRoot, publishVersion, false); + + if (pro) + { + XDocument proProps = XDocument.Load(proPropsPath); + string? currentProVersion = proProps.Root?.Element("PropertyGroup")?.Element("ProVersion")?.Value; + + version = await BumpVersionAsync(proRepoRoot, publishVersion, true); + + // Compare versions and use the higher one + if (CompareVersions(newCoreVersion, version) > 0 && + CompareVersions(currentCoreVersion ?? "0.0.0", currentProVersion ?? "0.0.0") > 0) + { + version = newCoreVersion; + } + else if (CompareVersions(newCoreVersion, version) < 0) + { + Console.WriteLine($"Core version ({newCoreVersion}) and Pro version ({version}) do not match after bumping. Please ensure both versions are the same in Directory.Build.props."); + } + + if (currentProVersion == version) + { + Console.WriteLine($"Pro Version is already set to {version}, no update needed."); + } + else + { + Console.WriteLine($"Updating Pro Version from {currentProVersion} to {version}"); + proProps.Root?.Element("PropertyGroup")?.SetElementValue("ProVersion", version); + proProps.Save(proPropsPath); + } + } + else + { + version = newCoreVersion; + } + + if (currentCoreVersion == version) + { + Console.WriteLine($"Core Version is already set to {version}, no update needed."); + } + else + { + Console.WriteLine($"Updating Core Version from {currentCoreVersion} to {version}"); + coreProps.Root?.Element("PropertyGroup")?.SetElementValue("CoreVersion", version); + coreProps.Save(corePropsPath); + } + + WriteStepCompleted(step, stepStartTime); + step++; + } + + // Check for another lock for the current configuration + if (File.Exists(currentCoreLockFilePath) && + File.GetLastWriteTimeUtc(currentCoreLockFilePath) < DateTime.UtcNow.AddSeconds(-5)) + { + Console.WriteLine("Another instance of the esBuild script is already running, please wait."); + while (File.Exists(currentCoreLockFilePath)) + { + Thread.Sleep(1000); + Console.Write("."); + } + Console.WriteLine("Lock released, continuing..."); + } + + // Step 4: Build Core JavaScript + stepStartTime = DateTime.Now; + WriteStepHeader(step, "Building Core JavaScript"); + + int esBuildResult = await RunDotnetScriptAsync(toolsDir, "ESBuild.dll", $"-c {configuration}"); + if (esBuildResult != 0) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"ERROR: ESBuild.dll failed with exit code {esBuildResult}. Exiting."); + Console.ResetColor(); + return 1; + } + + // Verify JavaScript files were created + string coreJsPath = Path.Combine(coreProjectPath, "wwwroot", "js"); + if (!Directory.Exists(coreJsPath) || Directory.GetFiles(coreJsPath, "*.js").Length == 0) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"WARNING: Core JavaScript files not found at {coreJsPath}, waiting..."); + Console.ResetColor(); + Thread.Sleep(2000); + if (!Directory.Exists(coreJsPath) || Directory.GetFiles(coreJsPath, "*.js").Length == 0) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("ERROR: Core JavaScript files still not found after waiting. Exiting."); + Console.ResetColor(); + return 1; + } + } + + WriteStepCompleted(step, stepStartTime); + step++; + + // Step 5: Restore .NET packages for Core + stepStartTime = DateTime.Now; + WriteStepHeader(step, "Restoring .NET Packages"); + + await RunDotnetCommand(coreProjectPath, "restore", "/p:PipelineBuild=true"); + + WriteStepCompleted(step, stepStartTime); + step++; + + // Step 6: Build Core Project and NuGet Package + stepStartTime = DateTime.Now; + WriteStepHeader(step, "Building Core Project and NuGet Package"); + + string[] coreBuildArgs = + [ + $"dymaptic.GeoBlazor.Core.csproj", + $"--no-restore", + "-c", + configuration, + $"/p:PipelineBuild=true", + $"/p:GenerateDocs={generateDocs.ToString().ToLower()}", + $"/p:GenerateXmlComments={generateXmlComments.ToString().ToLower()}", + $"/p:CoreVersion={version}", + $"/p:GeneratePackage={package.ToString().ToLower()}", + binlogFlag + ]; + + Console.WriteLine($"Executing 'dotnet build {string.Join(" ", coreBuildArgs)}'"); + + bool coreHasError = false; + for (int i = 1; i <= buildRetries; i++) + { + try + { + (int exitCode, List output) = await RunDotnetCommandWithOutputAsync(coreProjectPath, "build", coreBuildArgs); + + if (exitCode != 0) + { + Console.WriteLine($"ERROR: Core Build command failed with exit code {exitCode}. Exiting."); + coreHasError = true; + } + else + { + coreHasError = output.Any(line => + Regex.IsMatch(line, @"[1-9][0-9]* [Ee]rror(s)?") || + line.Contains("Build FAILED")); + if (!coreHasError) + { + break; + } + } + } + catch (Exception ex) + { + coreHasError = true; + Console.WriteLine($"Build attempt {i} of {buildRetries} failed with exception: {ex.Message}"); + } + + Console.WriteLine($"Build attempt {i} of {buildRetries} failed."); + if (i < buildRetries) + { + Console.WriteLine("Waiting 2 seconds before retrying..."); + Thread.Sleep(2000); + } + } + + if (coreHasError) + { + return 1; + } + + if (package) + { + // Copy generated NuGet package to repo root + string coreBinPath = Path.Combine(coreProjectPath, "bin", configuration); + if (Directory.Exists(coreBinPath)) + { + var coreNupkg = Directory.GetFiles(coreBinPath, "*.nupkg", SearchOption.AllDirectories) + .Select(f => new FileInfo(f)) + .OrderByDescending(f => f.LastWriteTime) + .FirstOrDefault(); + if (coreNupkg != null) + { + File.Copy(coreNupkg.FullName, Path.Combine(coreRepoRoot, coreNupkg.Name), true); + Console.WriteLine($"Copied {coreNupkg.Name} to {coreRepoRoot}"); + } + } + } + + WriteStepCompleted(step, stepStartTime); + step++; + + // Pro-specific steps + if (pro) + { + // Step 7: Restore Pro .NET packages + stepStartTime = DateTime.Now; + WriteStepHeader(step, "Restoring .NET Packages"); + + await RunDotnetCommand(proProjectPath, "restore", "/p:PipelineBuild=true"); + + WriteStepCompleted(step, stepStartTime); + step++; + + bool optOutFromObfuscation = !obfuscate; + + // Step 8: Build Validator (if exists) + if (Directory.Exists(validatorProjectPath)) + { + stepStartTime = DateTime.Now; + WriteStepHeader(step, $"Building Validator project in configuration {validatorConfig}"); + + // Set the ServerUrls in the Validator project + serverUrl = serverUrl.TrimEnd('/'); + Console.WriteLine($"Setting License Server Url to {serverUrl}"); + + string devBuildValidatorPath = Path.Combine(validatorProjectPath, "DevBuildValidator.cs"); + string publishTaskValidatorPath = Path.Combine(validatorProjectPath, "PublishTaskValidator.cs"); + + // Update DevBuildValidator.cs + string devValidatorContent = File.ReadAllText(devBuildValidatorPath); + devValidatorContent = Regex.Replace( + devValidatorContent, + @"public string SU \{ get; set; \} = null!;", + $"public string SU {{ get; set; }} = \"{serverUrl}/api/validate/v4\";"); + File.WriteAllText(devBuildValidatorPath, devValidatorContent); + Thread.Sleep(500); + + // Verify the update + devValidatorContent = File.ReadAllText(devBuildValidatorPath); + if (!devValidatorContent.Contains($"public string SU {{ get; set; }} = \"{serverUrl}/api/validate/v4\";")) + { + throw new Exception("Failed to set ServerUrl in DevBuildValidator.cs"); + } + + // Update PublishTaskValidator.cs + string publishValidatorContent = File.ReadAllText(publishTaskValidatorPath); + publishValidatorContent = Regex.Replace( + publishValidatorContent, + @"public string SU \{ get; set; \} = null!;", + $"public string SU {{ get; set; }} = \"{serverUrl}/api/validate/v4/publish\";"); + File.WriteAllText(publishTaskValidatorPath, publishValidatorContent); + Thread.Sleep(500); + + // Verify the update + publishValidatorContent = File.ReadAllText(publishTaskValidatorPath); + if (!publishValidatorContent.Contains($"public string SU {{ get; set; }} = \"{serverUrl}/api/validate/v4/publish\";")) + { + throw new Exception("Failed to set ServerUrl in PublishTaskValidator.cs"); + } + + // Build validator + string[] validatorBuildArgs = + [ + "dymaptic.GeoBlazor.Pro.V.csproj", + $"/p:OptOutFromObfuscation={optOutFromObfuscation.ToString().ToLower()}", + $"/p:ProVersion={version}", + "-c", + validatorConfig, + binlogFlag + ]; + + (int validatorExitCode, List validatorOutput) = await RunDotnetCommandWithOutputAsync(validatorProjectPath, "build", validatorBuildArgs); + + // Restore the ServerUrls in the Validator project + devValidatorContent = File.ReadAllText(devBuildValidatorPath); + devValidatorContent = Regex.Replace( + devValidatorContent, + @"public string SU \{ get; set; \} = "".*"";", + "public string SU { get; set; } = null!;"); + File.WriteAllText(devBuildValidatorPath, devValidatorContent); + + publishValidatorContent = File.ReadAllText(publishTaskValidatorPath); + publishValidatorContent = Regex.Replace( + publishValidatorContent, + @"public string SU \{ get; set; \} = "".*"";", + "public string SU { get; set; } = null!;"); + File.WriteAllText(publishTaskValidatorPath, publishValidatorContent); + + bool validatorHasError = validatorOutput.Any(line => + Regex.IsMatch(line, @"[1-9][0-9]* [Ee]rror(s)?") || + line.Contains("Build FAILED")); + if (validatorHasError) + { + return 1; + } + + WriteStepCompleted(step, stepStartTime); + step++; + } + + // Check for Pro lock + if (File.Exists(currentProLockFilePath) && + File.GetLastWriteTimeUtc(currentProLockFilePath) < DateTime.UtcNow.AddSeconds(-5)) + { + Console.WriteLine("Another instance of the esBuild scripts are already running, please wait."); + while (File.Exists(currentProLockFilePath)) + { + Thread.Sleep(1000); + Console.Write("."); + } + Console.WriteLine("Lock released, continuing..."); + } + + // Step 9: Build Pro JavaScript + stepStartTime = DateTime.Now; + WriteStepHeader(step, "Building Pro JavaScript"); + + int esProBuildResult = await RunDotnetScriptAsync(toolsDir, "ESBuild.dll", $"-c {configuration} --pro"); + if (esProBuildResult != 0) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"ERROR: esProBuild failed with exit code {esProBuildResult}. Exiting."); + Console.ResetColor(); + return 1; + } + + // Verify Pro JavaScript files were created + string proJsPath = Path.Combine(proProjectPath, "wwwroot", "js"); + if (!Directory.Exists(proJsPath) || Directory.GetFiles(proJsPath, "*.js").Length == 0) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"WARNING: Pro JavaScript files not found at {proJsPath}, waiting..."); + Console.ResetColor(); + Thread.Sleep(2000); + if (!Directory.Exists(proJsPath) || Directory.GetFiles(proJsPath, "*.js").Length == 0) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("ERROR: Pro JavaScript files still not found after waiting. Exiting."); + Console.ResetColor(); + return 1; + } + } + + WriteStepCompleted(step, stepStartTime); + step++; + + // Step 10: Build Pro project and package + stepStartTime = DateTime.Now; + WriteStepHeader(step, "Building Pro project and package"); + + string[] proBuildArgs = + [ + $"dymaptic.GeoBlazor.Pro.csproj", + "--no-restore", + $"-c", + configuration, + $"/p:PipelineBuild=true", + $"/p:GenerateDocs={generateDocs.ToString().ToLower()}", + $"/p:GenerateXmlComments={generateXmlComments.ToString().ToLower()}", + $"/p:CoreVersion={version}", + $"/p:ProVersion={version}", + $"/p:OptOutFromObfuscation={optOutFromObfuscation.ToString().ToLower()}", + $"/p:GeneratePackage={package.ToString().ToLower()}", + binlogFlag + ]; + + Console.WriteLine($"Executing 'dotnet {string.Join(" ", proBuildArgs)}'"); + + bool proHasError = false; + for (int i = 1; i <= buildRetries; i++) + { + try + { + (int exitCode, List output) = await RunDotnetCommandWithOutputAsync(proProjectPath, "build", proBuildArgs); + + if (exitCode != 0) + { + Console.WriteLine($"ERROR: Pro Build command failed with exit code {exitCode}. Exiting."); + proHasError = true; + } + else + { + proHasError = output.Any(line => + Regex.IsMatch(line, @"[1-9][0-9]* [Ee]rror(s)?") || + line.Contains("Build FAILED")); + if (!proHasError) + { + break; + } + } + } + catch (Exception ex) + { + proHasError = true; + Console.WriteLine($"Build attempt {i} of {buildRetries} failed with exception: {ex.Message}"); + } + + Console.WriteLine($"Build attempt {i} of {buildRetries} failed."); + if (i < buildRetries) + { + Console.WriteLine("Waiting 2 seconds before retrying..."); + Thread.Sleep(2000); + } + } + + if (proHasError) + { + return 1; + } + + if (package) + { + // Copy generated NuGet package to Core repo root + string proBinPath = Path.Combine(proProjectPath, "bin", configuration); + if (Directory.Exists(proBinPath)) + { + var proNupkg = Directory.GetFiles(proBinPath, "*.nupkg", SearchOption.AllDirectories) + .Select(f => new FileInfo(f)) + .OrderByDescending(f => f.LastWriteTime) + .FirstOrDefault(); + if (proNupkg != null) + { + File.Copy(proNupkg.FullName, Path.Combine(coreRepoRoot, proNupkg.Name), true); + Console.WriteLine($"Copied {proNupkg.Name} to {coreRepoRoot}"); + } + } + } + + WriteStepCompleted(step, stepStartTime); + } + + return 0; +} +catch (Exception ex) +{ + Console.WriteLine($"An error occurred: {ex.Message}"); + return 1; +} +finally +{ + TimeSpan totalTime = DateTime.Now - scriptStartTime; + + // Remove lock files + if (File.Exists(currentCoreLockFilePath)) + { + File.Delete(currentCoreLockFilePath); + } + if (File.Exists(currentProLockFilePath)) + { + File.Delete(currentProLockFilePath); + } + + Console.WriteLine(); + Console.BackgroundColor = ConsoleColor.DarkBlue; + Console.ForegroundColor = ConsoleColor.White; + Console.Write($"Total script execution time: {totalTime}."); + Console.ResetColor(); + Console.WriteLine(); +} + +// Helper methods + +static string GetScriptsDirectory([CallerFilePath] string? callerFilePath = null) +{ + // When running as a pre-compiled DLL, [CallerFilePath] contains the compile-time path + // which is invalid at runtime (especially in Docker containers). + // Detect this by checking if the file exists at the caller path. + if (!string.IsNullOrEmpty(callerFilePath) && File.Exists(callerFilePath)) + { + return Path.GetDirectoryName(callerFilePath)!; + } + + // Running as a DLL - use AppContext.BaseDirectory which points to the DLL location + // The DLL is in build-tools/, and scripts are in build-scripts/ (sibling directory) + string dllDirectory = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + return Path.Combine(Path.GetDirectoryName(dllDirectory)!, "build-scripts"); +} + +static void WriteStepHeader(int step, string description) +{ + Console.WriteLine(); + Console.BackgroundColor = ConsoleColor.DarkMagenta; + Console.ForegroundColor = ConsoleColor.White; + Console.Write($"{step}. {description}"); + Console.ResetColor(); + Console.WriteLine(); + Console.WriteLine(); +} + +static void WriteStepCompleted(int step, DateTime stepStartTime) +{ + TimeSpan elapsed = DateTime.Now - stepStartTime; + Console.BackgroundColor = ConsoleColor.Yellow; + Console.ForegroundColor = ConsoleColor.Black; + Console.Write($"Step {step} completed in {elapsed}."); + Console.ResetColor(); + Console.WriteLine(); +} + +static void DeleteDirectoryIfExists(string path, bool usePowerShell = false) +{ + if (!Directory.Exists(path)) + { + return; + } + + try + { + if (usePowerShell) + { + // Use PowerShell 7 for cross-platform deletion - handles long paths on Windows + string escapedPath = path.Replace("'", "''"); + var psi = new ProcessStartInfo + { + FileName = "pwsh", + Arguments = $"-NoProfile -Command \"Remove-Item -LiteralPath '{escapedPath}' -Recurse -Force -ErrorAction Stop\"", + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardError = true + }; + using var process = Process.Start(psi); + process?.WaitForExit(60000); // 60 second timeout for large node_modules + + // Verify deletion + if (Directory.Exists(path)) + { + Console.WriteLine($"WARNING: Failed to delete {path} with PowerShell, trying .NET fallback..."); + Directory.Delete(path, true); + } + } + else + { + Directory.Delete(path, true); + } + + // Verify and wait for filesystem to settle + int retries = 10; + while (Directory.Exists(path) && retries > 0) + { + Thread.Sleep(100); + retries--; + } + + if (Directory.Exists(path)) + { + Console.WriteLine($"WARNING: Directory {path} still exists after deletion attempt"); + } + } + catch (Exception ex) + { + Console.WriteLine($"WARNING: Failed to delete {path}: {ex.Message}"); + } +} + +static void DeleteDirectoryContentsIfExists(string path) +{ + if (Directory.Exists(path)) + { + try + { + foreach (string file in Directory.GetFiles(path, "*", SearchOption.AllDirectories)) + { + File.Delete(file); + } + } + catch + { + // Files may be locked - continue + } + } +} + +static async Task RunDotnetCommand(string workingDirectory, string command, params string[] args) +{ + var psi = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"{command} {string.Join(" ", args.Where(a => !string.IsNullOrWhiteSpace(a)))}", + WorkingDirectory = workingDirectory, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = Process.Start(psi); + if (process != null) + { + await process.WaitForExitAsync(); + } +} + +static async Task<(int ExitCode, List Output)> RunDotnetCommandWithOutputAsync(string workingDirectory, + string command, params string[] args) +{ + var output = new List(); + + var psi = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"{command} {string.Join(" ", args.Where(a => !string.IsNullOrWhiteSpace(a)))}", + WorkingDirectory = workingDirectory, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = Process.Start(psi); + if (process == null) + { + return (1, ["Failed to start dotnet"]); + } + + process.OutputDataReceived += (_, e) => + { + if (e.Data != null) + { + Console.WriteLine(e.Data); + output.Add(e.Data); + } + }; + + process.ErrorDataReceived += (_, e) => + { + if (e.Data != null) + { + Console.WriteLine(e.Data); + output.Add(e.Data); + } + }; + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + await process.WaitForExitAsync(); + + return (process.ExitCode, output); +} + +static async Task RunDotnetScriptAsync(string workingDirectory, string scriptName, string args) +{ + var psi = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"{scriptName} {args}", + WorkingDirectory = workingDirectory, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = Process.Start(psi); + if (process == null) + { + return 1; + } + + process.OutputDataReceived += (_, e) => + { + if (e.Data != null) + { + Console.WriteLine(e.Data); + } + }; + + process.ErrorDataReceived += (_, e) => + { + if (e.Data != null) + { + Console.WriteLine(e.Data); + } + }; + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + await process.WaitForExitAsync(); + + return process.ExitCode; +} + +static async Task BumpVersionAsync(string repoRoot, bool publish, bool isPro) +{ + string directoryBuildPropsPath = Path.Combine(repoRoot, "Directory.Build.props"); + XDocument propsContent = XDocument.Load(directoryBuildPropsPath); + + string? currentVersion = isPro + ? propsContent.Root?.Element("PropertyGroup")?.Element("ProVersion")?.Value + : propsContent.Root?.Element("PropertyGroup")?.Element("CoreVersion")?.Value; + + if (string.IsNullOrEmpty(currentVersion)) + { + throw new Exception($"Could not read {(isPro ? "Pro" : "Core")}Version from Directory.Build.props"); + } + + // Parse version: major.minor.patch.build-beta-betaVersion + var match = Regex.Match(currentVersion, @"(\d+)\.(\d+)\.(\d+)\.?(\d*)?-?(beta)?-?(\d*)?"); + if (!match.Success) + { + throw new Exception($"Could not parse version: {currentVersion}"); + } + + int majorVersion = int.Parse(match.Groups[1].Value); + int minorVersion = int.Parse(match.Groups[2].Value); + int patchVersion = int.Parse(match.Groups[3].Value); + int buildVersion = match.Groups[4].Success && !string.IsNullOrEmpty(match.Groups[4].Value) + ? int.Parse(match.Groups[4].Value) : 0; + bool isBeta = match.Groups[5].Success && !string.IsNullOrEmpty(match.Groups[5].Value); + int betaVersion = match.Groups[6].Success && !string.IsNullOrEmpty(match.Groups[6].Value) + ? int.Parse(match.Groups[6].Value) : 0; + + string newVersion; + + if (publish) + { + if (isBeta) + { + throw new Exception("Cannot publish a beta version. Please update the version in Directory.Build.props to a release version."); + } + + // Check the latest version on NuGet.org + string packageName = isPro ? "dymaptic.geoblazor.pro" : "dymaptic.geoblazor.core"; + string nugetUrl = $"https://azuresearch-usnc.nuget.org/query?q={packageName}&prerelease=false"; + + using var httpClient = new HttpClient(); + string response = await httpClient.GetStringAsync(nugetUrl); + using var doc = JsonDocument.Parse(response); + + string? latestVersion = null; + if (doc.RootElement.TryGetProperty("data", out var data) && data.GetArrayLength() > 0) + { + // Find the highest version + latestVersion = data.EnumerateArray() + .Select(d => d.GetProperty("version").GetString()) + .Where(v => v != null) + .OrderByDescending(v => Version.TryParse(v!.Split('-')[0], out var ver) ? ver : new Version(0, 0, 0)) + .FirstOrDefault(); + } + + if (string.IsNullOrEmpty(latestVersion)) + { + throw new Exception("Could not determine latest version from NuGet API."); + } + + var nugetMatch = Regex.Match(latestVersion, @"(\d+)\.(\d+)\.(\d+)\.?(\d*)?(-beta-)?(\d*)?"); + int nugetMajor = int.Parse(nugetMatch.Groups[1].Value); + int nugetMinor = int.Parse(nugetMatch.Groups[2].Value); + int nugetPatch = int.Parse(nugetMatch.Groups[3].Value); + + if (nugetMajor > majorVersion || + (nugetMajor == majorVersion && nugetMinor > minorVersion) || + (nugetMajor == majorVersion && nugetMinor == minorVersion && nugetPatch > patchVersion)) + { + throw new Exception("Version in NuGet is greater than local version. Please update the version in Directory.Build.props to match the latest version on Nuget.org."); + } + + if (nugetMajor == majorVersion && nugetMinor == minorVersion && nugetPatch == patchVersion) + { + // Increment the patch version for release + newVersion = $"{majorVersion}.{minorVersion}.{patchVersion + 1}"; + } + else + { + // Version is already higher than NuGet, just remove build number + newVersion = $"{majorVersion}.{minorVersion}.{patchVersion}"; + } + } + else + { + // For non-release builds, increment the build number + if (isBeta) + { + newVersion = $"{majorVersion}.{minorVersion}.{patchVersion}.{buildVersion}-beta-{betaVersion + 1}"; + } + else + { + newVersion = $"{majorVersion}.{minorVersion}.{patchVersion}.{buildVersion + 1}"; + } + } + + return newVersion; +} + +static int CompareVersions(string version1, string version2) +{ + // Simple version comparison - extract numeric parts + var v1Match = Regex.Match(version1, @"(\d+)\.(\d+)\.(\d+)\.?(\d*)?"); + var v2Match = Regex.Match(version2, @"(\d+)\.(\d+)\.(\d+)\.?(\d*)?"); + + if (!v1Match.Success || !v2Match.Success) + { + return string.Compare(version1, version2, StringComparison.Ordinal); + } + + int major1 = int.Parse(v1Match.Groups[1].Value); + int minor1 = int.Parse(v1Match.Groups[2].Value); + int patch1 = int.Parse(v1Match.Groups[3].Value); + int build1 = v1Match.Groups[4].Success && !string.IsNullOrEmpty(v1Match.Groups[4].Value) + ? int.Parse(v1Match.Groups[4].Value) : 0; + + int major2 = int.Parse(v2Match.Groups[1].Value); + int minor2 = int.Parse(v2Match.Groups[2].Value); + int patch2 = int.Parse(v2Match.Groups[3].Value); + int build2 = v2Match.Groups[4].Success && !string.IsNullOrEmpty(v2Match.Groups[4].Value) + ? int.Parse(v2Match.Groups[4].Value) : 0; + + if (major1 != major2) return major1.CompareTo(major2); + if (minor1 != minor2) return minor1.CompareTo(minor2); + if (patch1 != patch2) return patch1.CompareTo(patch2); + return build1.CompareTo(build2); +} diff --git a/build-scripts/ScriptBuilder.cs b/build-scripts/ScriptBuilder.cs new file mode 100644 index 000000000..2337cdf8c --- /dev/null +++ b/build-scripts/ScriptBuilder.cs @@ -0,0 +1,125 @@ +#!/usr/bin/env dotnet + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +// Builds all C# scripts in the current directory using dotnet build. +// Outputs built DLLs to ../build-tools/ +// Usage: dotnet ScriptBuilder.cs +// Can also pass in script names to build specific scripts only: +// Usage: dotnet ScriptBuilder.cs Script1.cs Script2.cs ... +// or the --exclude option to skip specific scripts: +// Usage: dotnet ScriptBuilder.cs --exclude Script1.cs Script2.cs ... + +bool excludeMode = false; +HashSet scriptsToProcess = new(); +string scriptDir = GetScriptDirectory(); +string outDir = Path.GetFullPath(Path.Combine(scriptDir, "..", "build-tools")); + +string[] scripts = Directory.GetFiles(scriptDir, "*.cs"); + +for (int i = 0; i < args.Length; i++) +{ + string arg = args[i].ToLowerInvariant(); + + switch (arg) + { + case "--exclude": + excludeMode = true; + break; + default: + scriptsToProcess.Add(arg); + break; + } +} + +foreach (string script in scripts) +{ + if (Path.GetFileName(script) == "ScriptBuilder.cs") + { + continue; + } + + if (scriptsToProcess.Count > 0) + { + if (excludeMode && scriptsToProcess.Contains(Path.GetFileName(script))) + { + Console.WriteLine($"Skipping excluded script: {Path.GetFileName(script)}"); + continue; + } + else if (!excludeMode && !scriptsToProcess.Contains(Path.GetFileName(script))) + { + Console.WriteLine($"Skipping unlisted script: {Path.GetFileName(script)}"); + continue; + } + } + + int returnCode = BuildScript(Path.GetFileName(script), scriptDir, outDir); + if (returnCode != 0) + { + return returnCode; + } +} + +return 0; + + + +static int BuildScript(string scriptName, string scriptDir, string outDir) +{ + string[] args = + [ + "build", + scriptName, + "-c", + "Release", + "-o", + outDir + ]; + + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = string.Join(" ", args), + WorkingDirectory = scriptDir, + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }; + + using Process? process = Process.Start(psi); + if (process is null) + { + Console.WriteLine($"Failed to build {scriptName}"); + return 1; + } + + // Read output asynchronously + process.OutputDataReceived += (sender, e) => + { + if (e.Data is not null) + { + Console.WriteLine(e.Data); + } + }; + + process.ErrorDataReceived += (sender, e) => + { + if (e.Data is not null) + { + Console.WriteLine(e.Data); + } + }; + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + process.WaitForExit(); + return process.ExitCode; +} + + +static string GetScriptDirectory([CallerFilePath] string? callerFilePath = null) +{ + return Path.GetDirectoryName(callerFilePath) ?? Environment.CurrentDirectory; +} \ No newline at end of file diff --git a/build-tools/BuildAppSettings b/build-tools/BuildAppSettings new file mode 100755 index 000000000..45ce8ddd2 Binary files /dev/null and b/build-tools/BuildAppSettings differ diff --git a/build-tools/BuildAppSettings.deps.json b/build-tools/BuildAppSettings.deps.json new file mode 100644 index 000000000..741cd3fa3 --- /dev/null +++ b/build-tools/BuildAppSettings.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v10.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v10.0": { + "BuildAppSettings/1.0.0": { + "runtime": { + "BuildAppSettings.dll": {} + } + } + } + }, + "libraries": { + "BuildAppSettings/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/build-tools/BuildAppSettings.dll b/build-tools/BuildAppSettings.dll new file mode 100644 index 000000000..b7ae08138 Binary files /dev/null and b/build-tools/BuildAppSettings.dll differ diff --git a/build-tools/BuildAppSettings.exe b/build-tools/BuildAppSettings.exe new file mode 100644 index 000000000..fb1f90b5e Binary files /dev/null and b/build-tools/BuildAppSettings.exe differ diff --git a/build-tools/BuildAppSettings.runtimeconfig.json b/build-tools/BuildAppSettings.runtimeconfig.json new file mode 100644 index 000000000..6766cb69d --- /dev/null +++ b/build-tools/BuildAppSettings.runtimeconfig.json @@ -0,0 +1,36 @@ +{ + "runtimeOptions": { + "tfm": "net10.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "10.0.0" + }, + "configProperties": { + "EntryPointFilePath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts/BuildAppSettings.cs", + "EntryPointFileDirectoryPath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts", + "Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability": true, + "System.ComponentModel.DefaultValueAttribute.IsSupported": false, + "System.ComponentModel.Design.IDesignerHost.IsSupported": false, + "System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization": false, + "System.ComponentModel.TypeDescriptor.IsComObjectDescriptorSupported": false, + "System.Data.DataSet.XmlSerializationIsSupported": false, + "System.Diagnostics.Tracing.EventSource.IsSupported": false, + "System.Linq.Enumerable.IsSizeOptimized": true, + "System.Net.SocketsHttpHandler.Http3Support": false, + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, + "System.Resources.ResourceManager.AllowCustomResourceTypes": false, + "System.Resources.UseSystemResourceKeys": false, + "System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported": false, + "System.Runtime.InteropServices.BuiltInComInterop.IsSupported": false, + "System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting": false, + "System.Runtime.InteropServices.EnableCppCLIHostActivation": false, + "System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop": false, + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false, + "System.StartupHookProvider.IsSupported": false, + "System.Text.Encoding.EnableUnsafeUTF7Encoding": false, + "System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault": false, + "System.Threading.Thread.EnableAutoreleasePool": false, + "System.Linq.Expressions.CanEmitObjectArrayDelegate": false + } + } +} \ No newline at end of file diff --git a/build-tools/BuildTemplates b/build-tools/BuildTemplates new file mode 100755 index 000000000..38b514fdb Binary files /dev/null and b/build-tools/BuildTemplates differ diff --git a/build-tools/BuildTemplates.deps.json b/build-tools/BuildTemplates.deps.json new file mode 100644 index 000000000..3e618ad81 --- /dev/null +++ b/build-tools/BuildTemplates.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v10.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v10.0": { + "BuildTemplates/1.0.0": { + "runtime": { + "BuildTemplates.dll": {} + } + } + } + }, + "libraries": { + "BuildTemplates/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/build-tools/BuildTemplates.dll b/build-tools/BuildTemplates.dll new file mode 100644 index 000000000..1a0e10405 Binary files /dev/null and b/build-tools/BuildTemplates.dll differ diff --git a/build-tools/BuildTemplates.exe b/build-tools/BuildTemplates.exe new file mode 100644 index 000000000..dd104b61f Binary files /dev/null and b/build-tools/BuildTemplates.exe differ diff --git a/build-tools/BuildTemplates.runtimeconfig.json b/build-tools/BuildTemplates.runtimeconfig.json new file mode 100644 index 000000000..97080ffdb --- /dev/null +++ b/build-tools/BuildTemplates.runtimeconfig.json @@ -0,0 +1,15 @@ +{ + "runtimeOptions": { + "tfm": "net10.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "10.0.0" + }, + "configProperties": { + "EntryPointFilePath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts/BuildTemplates.cs", + "EntryPointFileDirectoryPath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts", + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false + } + } +} \ No newline at end of file diff --git a/build-tools/ConsoleDialog b/build-tools/ConsoleDialog new file mode 100755 index 000000000..636dd088d Binary files /dev/null and b/build-tools/ConsoleDialog differ diff --git a/build-tools/ConsoleDialog.deps.json b/build-tools/ConsoleDialog.deps.json new file mode 100644 index 000000000..6a5d3eb14 --- /dev/null +++ b/build-tools/ConsoleDialog.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v10.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v10.0": { + "ConsoleDialog/1.0.0": { + "runtime": { + "ConsoleDialog.dll": {} + } + } + } + }, + "libraries": { + "ConsoleDialog/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/build-tools/ConsoleDialog.dll b/build-tools/ConsoleDialog.dll new file mode 100644 index 000000000..14d8c6e52 Binary files /dev/null and b/build-tools/ConsoleDialog.dll differ diff --git a/build-tools/ConsoleDialog.exe b/build-tools/ConsoleDialog.exe new file mode 100644 index 000000000..67de680ee Binary files /dev/null and b/build-tools/ConsoleDialog.exe differ diff --git a/build-tools/ConsoleDialog.runtimeconfig.json b/build-tools/ConsoleDialog.runtimeconfig.json new file mode 100644 index 000000000..033cf94ea --- /dev/null +++ b/build-tools/ConsoleDialog.runtimeconfig.json @@ -0,0 +1,36 @@ +{ + "runtimeOptions": { + "tfm": "net10.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "10.0.0" + }, + "configProperties": { + "EntryPointFilePath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts/ConsoleDialog.cs", + "EntryPointFileDirectoryPath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts", + "Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability": true, + "System.ComponentModel.DefaultValueAttribute.IsSupported": false, + "System.ComponentModel.Design.IDesignerHost.IsSupported": false, + "System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization": false, + "System.ComponentModel.TypeDescriptor.IsComObjectDescriptorSupported": false, + "System.Data.DataSet.XmlSerializationIsSupported": false, + "System.Diagnostics.Tracing.EventSource.IsSupported": false, + "System.Linq.Enumerable.IsSizeOptimized": true, + "System.Net.SocketsHttpHandler.Http3Support": false, + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, + "System.Resources.ResourceManager.AllowCustomResourceTypes": false, + "System.Resources.UseSystemResourceKeys": false, + "System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported": false, + "System.Runtime.InteropServices.BuiltInComInterop.IsSupported": false, + "System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting": false, + "System.Runtime.InteropServices.EnableCppCLIHostActivation": false, + "System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop": false, + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false, + "System.StartupHookProvider.IsSupported": false, + "System.Text.Encoding.EnableUnsafeUTF7Encoding": false, + "System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault": false, + "System.Threading.Thread.EnableAutoreleasePool": false, + "System.Linq.Expressions.CanEmitObjectArrayDelegate": false + } + } +} \ No newline at end of file diff --git a/build-tools/ESBuild b/build-tools/ESBuild new file mode 100755 index 000000000..41912328b Binary files /dev/null and b/build-tools/ESBuild differ diff --git a/build-tools/ESBuild.deps.json b/build-tools/ESBuild.deps.json new file mode 100644 index 000000000..a0835a4a7 --- /dev/null +++ b/build-tools/ESBuild.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v10.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v10.0": { + "ESBuild/1.0.0": { + "runtime": { + "ESBuild.dll": {} + } + } + } + }, + "libraries": { + "ESBuild/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/build-tools/ESBuild.dll b/build-tools/ESBuild.dll new file mode 100644 index 000000000..c51428bb3 Binary files /dev/null and b/build-tools/ESBuild.dll differ diff --git a/build-tools/ESBuild.exe b/build-tools/ESBuild.exe new file mode 100644 index 000000000..ca0e02ca4 Binary files /dev/null and b/build-tools/ESBuild.exe differ diff --git a/build-tools/ESBuild.runtimeconfig.json b/build-tools/ESBuild.runtimeconfig.json new file mode 100644 index 000000000..3b38c9acb --- /dev/null +++ b/build-tools/ESBuild.runtimeconfig.json @@ -0,0 +1,36 @@ +{ + "runtimeOptions": { + "tfm": "net10.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "10.0.0" + }, + "configProperties": { + "EntryPointFilePath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts/ESBuild.cs", + "EntryPointFileDirectoryPath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts", + "Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability": true, + "System.ComponentModel.DefaultValueAttribute.IsSupported": false, + "System.ComponentModel.Design.IDesignerHost.IsSupported": false, + "System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization": false, + "System.ComponentModel.TypeDescriptor.IsComObjectDescriptorSupported": false, + "System.Data.DataSet.XmlSerializationIsSupported": false, + "System.Diagnostics.Tracing.EventSource.IsSupported": false, + "System.Linq.Enumerable.IsSizeOptimized": true, + "System.Net.SocketsHttpHandler.Http3Support": false, + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, + "System.Resources.ResourceManager.AllowCustomResourceTypes": false, + "System.Resources.UseSystemResourceKeys": false, + "System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported": false, + "System.Runtime.InteropServices.BuiltInComInterop.IsSupported": false, + "System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting": false, + "System.Runtime.InteropServices.EnableCppCLIHostActivation": false, + "System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop": false, + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false, + "System.StartupHookProvider.IsSupported": false, + "System.Text.Encoding.EnableUnsafeUTF7Encoding": false, + "System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault": false, + "System.Threading.Thread.EnableAutoreleasePool": false, + "System.Linq.Expressions.CanEmitObjectArrayDelegate": false + } + } +} \ No newline at end of file diff --git a/build-tools/ESBuildClearLocks b/build-tools/ESBuildClearLocks new file mode 100755 index 000000000..bb0a0a176 Binary files /dev/null and b/build-tools/ESBuildClearLocks differ diff --git a/build-tools/ESBuildClearLocks.deps.json b/build-tools/ESBuildClearLocks.deps.json new file mode 100644 index 000000000..4bfbba92d --- /dev/null +++ b/build-tools/ESBuildClearLocks.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v10.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v10.0": { + "ESBuildClearLocks/1.0.0": { + "runtime": { + "ESBuildClearLocks.dll": {} + } + } + } + }, + "libraries": { + "ESBuildClearLocks/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/build-tools/ESBuildClearLocks.dll b/build-tools/ESBuildClearLocks.dll new file mode 100644 index 000000000..26bb90e7b Binary files /dev/null and b/build-tools/ESBuildClearLocks.dll differ diff --git a/build-tools/ESBuildClearLocks.exe b/build-tools/ESBuildClearLocks.exe new file mode 100644 index 000000000..14ba34676 Binary files /dev/null and b/build-tools/ESBuildClearLocks.exe differ diff --git a/build-tools/ESBuildClearLocks.runtimeconfig.json b/build-tools/ESBuildClearLocks.runtimeconfig.json new file mode 100644 index 000000000..555ae6101 --- /dev/null +++ b/build-tools/ESBuildClearLocks.runtimeconfig.json @@ -0,0 +1,36 @@ +{ + "runtimeOptions": { + "tfm": "net10.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "10.0.0" + }, + "configProperties": { + "EntryPointFilePath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts/ESBuildClearLocks.cs", + "EntryPointFileDirectoryPath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts", + "Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability": true, + "System.ComponentModel.DefaultValueAttribute.IsSupported": false, + "System.ComponentModel.Design.IDesignerHost.IsSupported": false, + "System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization": false, + "System.ComponentModel.TypeDescriptor.IsComObjectDescriptorSupported": false, + "System.Data.DataSet.XmlSerializationIsSupported": false, + "System.Diagnostics.Tracing.EventSource.IsSupported": false, + "System.Linq.Enumerable.IsSizeOptimized": true, + "System.Net.SocketsHttpHandler.Http3Support": false, + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, + "System.Resources.ResourceManager.AllowCustomResourceTypes": false, + "System.Resources.UseSystemResourceKeys": false, + "System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported": false, + "System.Runtime.InteropServices.BuiltInComInterop.IsSupported": false, + "System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting": false, + "System.Runtime.InteropServices.EnableCppCLIHostActivation": false, + "System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop": false, + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false, + "System.StartupHookProvider.IsSupported": false, + "System.Text.Encoding.EnableUnsafeUTF7Encoding": false, + "System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault": false, + "System.Threading.Thread.EnableAutoreleasePool": false, + "System.Linq.Expressions.CanEmitObjectArrayDelegate": false + } + } +} \ No newline at end of file diff --git a/build-tools/ESBuildWaitForCompletion b/build-tools/ESBuildWaitForCompletion new file mode 100755 index 000000000..61b724649 Binary files /dev/null and b/build-tools/ESBuildWaitForCompletion differ diff --git a/build-tools/ESBuildWaitForCompletion.deps.json b/build-tools/ESBuildWaitForCompletion.deps.json new file mode 100644 index 000000000..4e271f070 --- /dev/null +++ b/build-tools/ESBuildWaitForCompletion.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v10.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v10.0": { + "ESBuildWaitForCompletion/1.0.0": { + "runtime": { + "ESBuildWaitForCompletion.dll": {} + } + } + } + }, + "libraries": { + "ESBuildWaitForCompletion/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/build-tools/ESBuildWaitForCompletion.dll b/build-tools/ESBuildWaitForCompletion.dll new file mode 100644 index 000000000..38f2439b9 Binary files /dev/null and b/build-tools/ESBuildWaitForCompletion.dll differ diff --git a/build-tools/ESBuildWaitForCompletion.exe b/build-tools/ESBuildWaitForCompletion.exe new file mode 100644 index 000000000..e707c8c89 Binary files /dev/null and b/build-tools/ESBuildWaitForCompletion.exe differ diff --git a/build-tools/ESBuildWaitForCompletion.runtimeconfig.json b/build-tools/ESBuildWaitForCompletion.runtimeconfig.json new file mode 100644 index 000000000..3e7d1efdf --- /dev/null +++ b/build-tools/ESBuildWaitForCompletion.runtimeconfig.json @@ -0,0 +1,36 @@ +{ + "runtimeOptions": { + "tfm": "net10.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "10.0.0" + }, + "configProperties": { + "EntryPointFilePath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts/ESBuildWaitForCompletion.cs", + "EntryPointFileDirectoryPath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts", + "Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability": true, + "System.ComponentModel.DefaultValueAttribute.IsSupported": false, + "System.ComponentModel.Design.IDesignerHost.IsSupported": false, + "System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization": false, + "System.ComponentModel.TypeDescriptor.IsComObjectDescriptorSupported": false, + "System.Data.DataSet.XmlSerializationIsSupported": false, + "System.Diagnostics.Tracing.EventSource.IsSupported": false, + "System.Linq.Enumerable.IsSizeOptimized": true, + "System.Net.SocketsHttpHandler.Http3Support": false, + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, + "System.Resources.ResourceManager.AllowCustomResourceTypes": false, + "System.Resources.UseSystemResourceKeys": false, + "System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported": false, + "System.Runtime.InteropServices.BuiltInComInterop.IsSupported": false, + "System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting": false, + "System.Runtime.InteropServices.EnableCppCLIHostActivation": false, + "System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop": false, + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false, + "System.StartupHookProvider.IsSupported": false, + "System.Text.Encoding.EnableUnsafeUTF7Encoding": false, + "System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault": false, + "System.Threading.Thread.EnableAutoreleasePool": false, + "System.Linq.Expressions.CanEmitObjectArrayDelegate": false + } + } +} \ No newline at end of file diff --git a/build-tools/FetchNuGetVersion b/build-tools/FetchNuGetVersion new file mode 100755 index 000000000..077ee4a1f Binary files /dev/null and b/build-tools/FetchNuGetVersion differ diff --git a/build-tools/FetchNuGetVersion.deps.json b/build-tools/FetchNuGetVersion.deps.json new file mode 100644 index 000000000..079217a0b --- /dev/null +++ b/build-tools/FetchNuGetVersion.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v10.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v10.0": { + "FetchNuGetVersion/1.0.0": { + "runtime": { + "FetchNuGetVersion.dll": {} + } + } + } + }, + "libraries": { + "FetchNuGetVersion/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/build-tools/FetchNuGetVersion.dll b/build-tools/FetchNuGetVersion.dll new file mode 100644 index 000000000..152c8ebf0 Binary files /dev/null and b/build-tools/FetchNuGetVersion.dll differ diff --git a/build-tools/FetchNuGetVersion.exe b/build-tools/FetchNuGetVersion.exe new file mode 100644 index 000000000..f58d02133 Binary files /dev/null and b/build-tools/FetchNuGetVersion.exe differ diff --git a/build-tools/FetchNuGetVersion.runtimeconfig.json b/build-tools/FetchNuGetVersion.runtimeconfig.json new file mode 100644 index 000000000..a1cdfde9a --- /dev/null +++ b/build-tools/FetchNuGetVersion.runtimeconfig.json @@ -0,0 +1,36 @@ +{ + "runtimeOptions": { + "tfm": "net10.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "10.0.0" + }, + "configProperties": { + "EntryPointFilePath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts/FetchNuGetVersion.cs", + "EntryPointFileDirectoryPath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts", + "Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability": true, + "System.ComponentModel.DefaultValueAttribute.IsSupported": false, + "System.ComponentModel.Design.IDesignerHost.IsSupported": false, + "System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization": false, + "System.ComponentModel.TypeDescriptor.IsComObjectDescriptorSupported": false, + "System.Data.DataSet.XmlSerializationIsSupported": false, + "System.Diagnostics.Tracing.EventSource.IsSupported": false, + "System.Linq.Enumerable.IsSizeOptimized": true, + "System.Net.SocketsHttpHandler.Http3Support": false, + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, + "System.Resources.ResourceManager.AllowCustomResourceTypes": false, + "System.Resources.UseSystemResourceKeys": false, + "System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported": false, + "System.Runtime.InteropServices.BuiltInComInterop.IsSupported": false, + "System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting": false, + "System.Runtime.InteropServices.EnableCppCLIHostActivation": false, + "System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop": false, + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false, + "System.StartupHookProvider.IsSupported": false, + "System.Text.Encoding.EnableUnsafeUTF7Encoding": false, + "System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault": false, + "System.Threading.Thread.EnableAutoreleasePool": false, + "System.Linq.Expressions.CanEmitObjectArrayDelegate": false + } + } +} \ No newline at end of file diff --git a/build-tools/GeoBlazorBuild b/build-tools/GeoBlazorBuild new file mode 100755 index 000000000..d263ef349 Binary files /dev/null and b/build-tools/GeoBlazorBuild differ diff --git a/build-tools/GeoBlazorBuild.deps.json b/build-tools/GeoBlazorBuild.deps.json new file mode 100644 index 000000000..d70590fb0 --- /dev/null +++ b/build-tools/GeoBlazorBuild.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v10.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v10.0": { + "GeoBlazorBuild/1.0.0": { + "runtime": { + "GeoBlazorBuild.dll": {} + } + } + } + }, + "libraries": { + "GeoBlazorBuild/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/build-tools/GeoBlazorBuild.dll b/build-tools/GeoBlazorBuild.dll new file mode 100644 index 000000000..2e89f12cb Binary files /dev/null and b/build-tools/GeoBlazorBuild.dll differ diff --git a/build-tools/GeoBlazorBuild.exe b/build-tools/GeoBlazorBuild.exe new file mode 100644 index 000000000..a41d208b2 Binary files /dev/null and b/build-tools/GeoBlazorBuild.exe differ diff --git a/build-tools/GeoBlazorBuild.runtimeconfig.json b/build-tools/GeoBlazorBuild.runtimeconfig.json new file mode 100644 index 000000000..789dbb8d2 --- /dev/null +++ b/build-tools/GeoBlazorBuild.runtimeconfig.json @@ -0,0 +1,36 @@ +{ + "runtimeOptions": { + "tfm": "net10.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "10.0.0" + }, + "configProperties": { + "EntryPointFilePath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts/GeoBlazorBuild.cs", + "EntryPointFileDirectoryPath": "/home/runner/work/GeoBlazor/GeoBlazor/build-scripts", + "Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability": true, + "System.ComponentModel.DefaultValueAttribute.IsSupported": false, + "System.ComponentModel.Design.IDesignerHost.IsSupported": false, + "System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization": false, + "System.ComponentModel.TypeDescriptor.IsComObjectDescriptorSupported": false, + "System.Data.DataSet.XmlSerializationIsSupported": false, + "System.Diagnostics.Tracing.EventSource.IsSupported": false, + "System.Linq.Enumerable.IsSizeOptimized": true, + "System.Net.SocketsHttpHandler.Http3Support": false, + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, + "System.Resources.ResourceManager.AllowCustomResourceTypes": false, + "System.Resources.UseSystemResourceKeys": false, + "System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported": false, + "System.Runtime.InteropServices.BuiltInComInterop.IsSupported": false, + "System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting": false, + "System.Runtime.InteropServices.EnableCppCLIHostActivation": false, + "System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop": false, + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false, + "System.StartupHookProvider.IsSupported": false, + "System.Text.Encoding.EnableUnsafeUTF7Encoding": false, + "System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault": false, + "System.Threading.Thread.EnableAutoreleasePool": false, + "System.Linq.Expressions.CanEmitObjectArrayDelegate": false + } + } +} \ No newline at end of file diff --git a/buildAppSettings.ps1 b/buildAppSettings.ps1 deleted file mode 100644 index e49d22acc..000000000 --- a/buildAppSettings.ps1 +++ /dev/null @@ -1,84 +0,0 @@ -<# -.SYNOPSIS - Generates appsettings.json files for test applications. - -.DESCRIPTION - Creates appsettings.json files at the specified paths with the provided configuration values. - -.PARAMETER ArcGISApiKey - The ArcGIS API key for map services. - -.PARAMETER LicenseKey - The GeoBlazor license key. - -.PARAMETER OutputPaths - Array of file paths where appsettings.json should be written. - -.PARAMETER DocsUrl - The documentation URL. Defaults to "https://docs.geoblazor.com". - -.PARAMETER ByPassApiKey - The API bypass key for samples. - -.PARAMETER WfsServers - Additional WFS server configuration (JSON fragment without outer braces). - -.EXAMPLE - ./buildAppSettings.ps1 -ArcGISApiKey "your-key" -LicenseKey "your-license" -OutputPaths @("./appsettings.json") - -.EXAMPLE - ./buildAppSettings.ps1 -ArcGISApiKey "key" -LicenseKey "license" -OutputPaths @("./app1/appsettings.json", "./app2/appsettings.json") -#> - -param( - [Parameter(Mandatory = $true)] - [string]$ArcGISApiKey, - - [Parameter(Mandatory = $true)] - [string]$LicenseKey, - - [Parameter(Mandatory = $true)] - [string[]]$OutputPaths, - - [Parameter(Mandatory = $false)] - [string]$DocsUrl = "https://docs.geoblazor.com", - - [Parameter(Mandatory = $false)] - [string]$ByPassApiKey = "", - - [Parameter(Mandatory = $false)] - [string]$WfsServers = "" -) - -# Build the appsettings JSON content -$appSettingsContent = @" -{ - "ArcGISApiKey": "$ArcGISApiKey", - "GeoBlazor": { - "LicenseKey": "$LicenseKey" - }, - "DocsUrl": "$DocsUrl", - "ByPassApiKey": "$ByPassApiKey" -"@ - -# Add WFS servers if provided -if ($WfsServers -ne "") { - $appSettingsContent += ",`n $WfsServers" -} - -$appSettingsContent += "`n}" - -# Write to each target path -foreach ($path in $OutputPaths) { - $directory = Split-Path -Parent $path - if ($directory -and !(Test-Path $directory)) { - New-Item -ItemType Directory -Path $directory -Force | Out-Null - } - if (!(Test-Path $path)) { - New-Item -ItemType File -Path $path -Force | Out-Null - } - $appSettingsContent | Out-File -FilePath $path -Encoding utf8 - Write-Host "Created: $path" -} - -Write-Host "AppSettings files generated successfully." diff --git a/bumpVersion.ps1 b/bumpVersion.ps1 deleted file mode 100644 index 73ac00b90..000000000 --- a/bumpVersion.ps1 +++ /dev/null @@ -1,88 +0,0 @@ -# Increments and returns the next version number based on the current version in Directory.Build.props -# If -publish is specified, checks against nuget.org and increments the patch version if needed -# If -test is specified, uses that version instead of reading from Directory.Build.props (for testing purposes) -# If neither is specified, increments the build number (or beta number if it's a beta version) -# example: .\bumpVersion.ps1 -test 1.2.3 ---- would test the version bump from 1.2.3 to 1.2.4 -# example: .\bumpVersion.ps1 -publish ---- compares against nuget, increments the 3rd number by one -param([switch]$Pro, [switch][Alias("pub")]$Publish, [string]$Test) - -## Read Directory.Build.Props to get the version number and increment it -$RepoRoot = $Pro ? (Join-Path $PSScriptRoot "..") : ($PSScriptRoot) - -$DirectoryBuildPropsPath = Join-Path -Path $RepoRoot "Directory.Build.props" -[xml]$PropsContent = [xml](Get-Content $DirectoryBuildPropsPath) -$CurrentVersion = $Pro ? $PropsContent.Project.PropertyGroup.ProVersion : $PropsContent.Project.PropertyGroup.CoreVersion - -if ($null -ne $Test -and $Test -ne ""){ - $CurrentVersion = $Test -} - -$null = $CurrentVersion -match '(\d+)\.(\d+)\.(\d+)\.?(\d*)?-?(beta)?-?(\d*)?' -$MajorVersion = [int]$matches[1] -$MinorVersion = [int]$matches[2] -$PatchVersion = [int]$matches[3] -$BuildVersion = if ($matches.Count -gt 4) { [int]$matches[4] } else { 0 } -$IsBeta = if ($matches.Count -gt 5) { $null -ne $matches[5] } else { $false } -$BetaVersion = if ($matches.Count -gt 6) { [int]$matches[6] } else { 0 } -$NewVersion = "" - -if ($Publish) -{ - if ($IsBeta) - { - throw "Cannot publish a beta version. Please update the version in Directory.Build.props to a release version." - } - - ## Check the latest version on Nuget.org using web API - $NuGetUrl = $Pro ` - ? "https://azuresearch-usnc.nuget.org/query?q=dymaptic.geoblazor.pro&prerelease=false" ` - : "https://azuresearch-usnc.nuget.org/query?q=dymaptic.geoblazor.core&prerelease=false" - $Response = Invoke-RestMethod -Uri $NuGetUrl -Method Get - $LatestVersion = $null - if ($Response.data.Count -gt 0) { - # Find the highest version (should be first, but sort just in case) - $LatestVersion = ($Response.data | Sort-Object { [version]$_.version } -Descending)[0].version - } - if ($null -eq $LatestVersion) { - throw "Could not determine latest version from NuGet API." - } - $null = $LatestVersion -match '(\d+)\.(\d+)\.(\d+)\.?(\d*)?(-beta-)?(\d*)?' - $NuGetMajorVersion = [int]$matches[1] - $NuGetMinorVersion = [int]$matches[2] - $NuGetPatchVersion = [int]$matches[3] - $NuGetBuildVersion = if ($matches.Count -gt 4) { [int]$matches[4] } else { 0 } - - if ($NuGetMajorVersion -gt $MajorVersion ` - -or ($NuGetMajorVersion -eq $MajorVersion -and $NuGetMinorVersion -gt $MinorVersion) ` - -or ($NuGetMajorVersion -eq $MajorVersion -and $NuGetMinorVersion -eq $MinorVersion -and $NuGetPatchVersion -gt $PatchVersion)) - { - throw "Version in Nuget is greater than local version. Please update the version in Directory.Build.props to match the latest version on Nuget.org." - } - - if ($NuGetMajorVersion -eq $MajorVersion -and $NuGetMinorVersion -eq $MinorVersion -and $NuGetPatchVersion -eq $PatchVersion) - { - # Increment the patch version for release - $NewVersion = "$MajorVersion.$MinorVersion.$($PatchVersion + 1)" - } - else - { - # Chop off the build version, we don't need to publish that, but the version is already higher than nuget. - $NewVersion = "$MajorVersion.$MinorVersion.$PatchVersion" - } -} -else -{ - # For non-release builds, increment the build number - if ($IsBeta) - { - # Increment the beta version - $NewVersion = "$MajorVersion.$MinorVersion.$PatchVersion.$BuildVersion-beta-$($BetaVersion + 1)" - } - else - { - # Increment the build version for local tracking - $NewVersion = "$MajorVersion.$MinorVersion.$PatchVersion.$([int]$BuildVersion + 1)" - } -} - -return $NewVersion \ No newline at end of file diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index f28c635a7..d890f9ec7 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -190,9 +190,11 @@ that normal Blazor components do not have. - Create a new Widget samples page in `dymaptic.GeoBlazor.Core.Samples.Shared/Pages`. Also add to the `NavMenu.razor`. - Alternatively, for simple widgets, you can add them to the `Widgets.razor` sample. - Create a new unit test in `dymaptic.GeoBlazor.Core.Tests.Blazor.Shared/Components/WidgetTests.razor`. + ## Automated Browser Testing -GeoBlazor includes a comprehensive automated testing framework using Playwright and MSTest. For detailed documentation, see the [Test Automation README](../test/dymaptic.GeoBlazor.Core.Test.Automation/README.md). +GeoBlazor includes a comprehensive automated testing framework using Playwright and MSTest. For detailed documentation, +see the [Test Automation README](../test/dymaptic.GeoBlazor.Core.Test.Automation/README.md). ### Quick Start @@ -209,7 +211,8 @@ dotnet test -e USE_CONTAINER=true ### Key Features -- **Auto-generated tests**: A source generator scans test components in `dymaptic.GeoBlazor.Core.Test.Blazor.Shared` and generates MSTest classes +- **Auto-generated tests**: A source generator scans test components in `dymaptic.GeoBlazor.Core.Test.Blazor.Shared` and + generates MSTest classes - **Browser pooling**: Limits concurrent browser instances to prevent resource exhaustion in CI environments - **Docker support**: Can run test applications in Docker containers for consistent CI/CD environments - **Parallel execution**: Tests run in parallel at the method level with browser pool management @@ -232,6 +235,7 @@ public async Task MyNewTest() ### Configuration Set environment variables for test configuration: + - `ARCGIS_API_KEY`: Required ArcGIS API key - `GEOBLAZOR_CORE_LICENSE_KEY`: Core license key - `USE_CONTAINER`: Set to `true` for container mode diff --git a/esBuildClearLocks.ps1 b/esBuildClearLocks.ps1 deleted file mode 100644 index 335c11438..000000000 --- a/esBuildClearLocks.ps1 +++ /dev/null @@ -1,22 +0,0 @@ -$CoreDebugLock = Join-Path $PSScriptRoot "src/dymaptic.GeoBlazor.Core/esBuild.Debug.lock" -$CoreReleaseLock = Join-Path $PSScriptRoot "src/dymaptic.GeoBlazor.Core/esBuild.Release.lock" -$ProDebugLock = Join-Path $PSScriptRoot "../src/dymaptic.GeoBlazor.Pro/esProBuild.Debug.lock" -$ProReleaseLock = Join-Path $PSScriptRoot "../src/dymaptic.GeoBlazor.Pro/esProBuild.Release.lock" -if (Test-Path $CoreDebugLock) -{ - Remove-Item -Path $CoreDebugLock -Force -} -if (Test-Path $CoreReleaseLock) -{ - Remove-Item -Path $CoreReleaseLock -Force -} -if (Test-Path $ProDebugLock) -{ - Remove-Item -Path $ProDebugLock -Force -} -if (Test-Path $ProReleaseLock) -{ - Remove-Item -Path $ProReleaseLock -Force -} - -Write-Host "Cleared esBuild lock files" \ No newline at end of file diff --git a/esBuildWaitForCompletion.ps1 b/esBuildWaitForCompletion.ps1 deleted file mode 100644 index c7254199f..000000000 --- a/esBuildWaitForCompletion.ps1 +++ /dev/null @@ -1,42 +0,0 @@ -# PowerShell -param([string][Alias("c")]$Configuration = "Debug") - -$CoreRootDir = Join-Path -Path $PSScriptRoot "src\dymaptic.GeoBlazor.Core" -$ProRootDir = Join-Path -Path $PSScriptRoot "..\src\dymaptic.GeoBlazor.Pro" -$CoreLockFilePath = Join-Path -Path $CoreRootDir "esBuild.$Configuration.lock" -$ProLockFilePath = Join-Path -Path $ProRootDir "esBuild.$Configuration.lock" - -Write-Host "Waiting for lock files: $CoreLockFilePath, $ProLockFilePath" - -if ((Test-Path -Path $CoreLockFilePath) -or (Test-Path -Path $ProLockFilePath)) { - Write-Host "Lock file found. Waiting for release." -} else { - Write-Host "No lock file found. Exiting." - return 0 -} - -$timeout = 30 -$elapsed = 0 - -while ((Test-Path -Path $CoreLockFilePath) -or (Test-Path -Path $ProLockFilePath)) { - Start-Sleep -Seconds 1 - Write-Host -NoNewline "." - $elapsed++ - - if ($elapsed -ge $timeout) { - Write-Host "" - Write-Host "Timeout reached ($timeout seconds). Deleting lock files." - if (Test-Path -Path $CoreLockFilePath) { - Remove-Item -Path $CoreLockFilePath -Force - Write-Host "Deleted: $CoreLockFilePath" - } - if (Test-Path -Path $ProLockFilePath) { - Remove-Item -Path $ProLockFilePath -Force - Write-Host "Deleted: $ProLockFilePath" - } - break - } -} - -Write-Host "Lock file removed. Exiting." -exit 0 diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Maui/dymaptic.GeoBlazor.Core.Sample.Maui.csproj b/samples/dymaptic.GeoBlazor.Core.Sample.Maui/dymaptic.GeoBlazor.Core.Sample.Maui.csproj index e48473e4d..c2a930d9a 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.Maui/dymaptic.GeoBlazor.Core.Sample.Maui.csproj +++ b/samples/dymaptic.GeoBlazor.Core.Sample.Maui/dymaptic.GeoBlazor.Core.Sample.Maui.csproj @@ -1,8 +1,8 @@  - net10.0-android;net10.0-ios;net10.0-maccatalyst - $(TargetFrameworks);net10.0-windows10.0.19041.0 + net10.0-android;net10.0-ios;net10.0-maccatalyst + $(TargetFrameworks);net10.0-windows10.0.19041.0 Exe dymaptic.GeoBlazor.Core.Sample.Maui true @@ -52,11 +52,11 @@ - - - - - + + + + + diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.Client/Routes.razor b/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.Client/Routes.razor index 9abe6bd82..de8d62207 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.Client/Routes.razor +++ b/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.Client/Routes.razor @@ -1,6 +1,7 @@ - +@using dymaptic.GeoBlazor.Core.Sample.OAuth.Client.Layout + - + diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.Client/dymaptic.GeoBlazor.Core.Sample.OAuth.Client.csproj b/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.Client/dymaptic.GeoBlazor.Core.Sample.OAuth.Client.csproj index 9e088556d..212ce0fb7 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.Client/dymaptic.GeoBlazor.Core.Sample.OAuth.Client.csproj +++ b/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.Client/dymaptic.GeoBlazor.Core.Sample.OAuth.Client.csproj @@ -1,17 +1,14 @@ - net10.0 - enable - enable + net10.0 true Default - true - - + + diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth/Program.cs b/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth/Program.cs index 491f4e9b2..48e89b9f1 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth/Program.cs +++ b/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth/Program.cs @@ -1,5 +1,7 @@ -using dymaptic.GeoBlazor.Core.Sample.OAuth.Components; using dymaptic.GeoBlazor.Core; +using dymaptic.GeoBlazor.Core.Sample.OAuth.Components; +using _Imports = dymaptic.GeoBlazor.Core.Sample.OAuth.Client._Imports; + var builder = WebApplication.CreateBuilder(args); @@ -20,6 +22,7 @@ else { app.UseExceptionHandler("/Error", createScopeForErrors: true); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } @@ -35,6 +38,6 @@ app.MapRazorComponents() .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode() - .AddAdditionalAssemblies(typeof(dymaptic.GeoBlazor.Core.Sample.OAuth.Client._Imports).Assembly); + .AddAdditionalAssemblies(typeof(_Imports).Assembly); -app.Run(); +app.Run(); \ No newline at end of file diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.csproj b/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.csproj index 6ecd14131..e99eb002d 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.csproj +++ b/samples/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth/dymaptic.GeoBlazor.Core.Sample.OAuth.csproj @@ -1,16 +1,14 @@ - net10.0 - enable - enable + net10.0 aspnet-dymaptic.GeoBlazor.Core.Sample.OAuth-881b5a42-0b71-4c8c-9901-8d12693bd109 - + diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Basemaps.razor b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Basemaps.razor index 5e6ee8b4e..6799b3470 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Basemaps.razor +++ b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Basemaps.razor @@ -36,14 +36,16 @@ } } - Basemap Style Service + Basemap + Style Service