fix(vehicles): replace the personal vehicle when the server forces re… #105
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI/CD (Enhanced) | |
| # Build + release pipeline for vMenu Enhanced (FiveM Enhanced, .NET 10). | |
| # | |
| # This is the ACTIVE Enhanced pipeline. While the project is in alpha it only | |
| # ever produces DRAFT PRE-RELEASES, versioned 0.0.1-alpha.<run_number> and tagged | |
| # with the `enhanced-v` prefix so the Enhanced version lineage stays fully | |
| # separate from legacy vMenu's `v` tags. | |
| # | |
| # Later, once master is renamed to legacy, the combined workflow | |
| # (.github/workflows/combined.yml) takes over cutting the shared release and | |
| # the release job below can be switched off. | |
| on: | |
| push: | |
| branches: | |
| - enhanced | |
| # Docs live in /docs and deploy via docs.yml - don't trigger the .NET | |
| # build/release pipeline (and cut a spurious release) on docs-only commits. | |
| paths-ignore: | |
| - "docs/**" | |
| - ".github/workflows/docs.yml" | |
| # Build (but never release) pull requests, so PRs into enhanced get validated. | |
| pull_request: | |
| permissions: | |
| contents: write | |
| # So the build can leave its translation coverage table on a pull request. | |
| pull-requests: write | |
| # A run that only validates is cancelled as soon as a newer push replaces it, there being | |
| # no point checking a commit that has already been superseded. A run on `enhanced` cuts a | |
| # release, a tag and a NuGet package, so it is left alone once started: cancelling it halfway | |
| # loses a version that then never ships. GitHub still only keeps ONE run pending per group, | |
| # so a third push cancels the second while it is still queued and never touches the first. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ !(github.event_name == 'push' && github.ref == 'refs/heads/enhanced') }} | |
| env: | |
| DOTNET_NOLOGO: true | |
| DOTNET_CLI_TELEMETRY_OPTOUT: true | |
| # vMenu Enhanced version lineage. While in alpha the base stays fixed and the | |
| # build number is appended => 0.0.1-alpha.<run_number>. Bump this manually | |
| # when promoting to beta, and again when moving to stable 1.0.0 releases. | |
| ENHANCED_VERSION_BASE: "0.0.1-alpha" | |
| jobs: | |
| # ============================================================ | |
| # VERSION - derives the alpha version from the base + run number | |
| # enhanced-v0.0.1-alpha.<run_number> | |
| # ============================================================ | |
| version: | |
| name: Calculate Version | |
| runs-on: ubuntu-latest | |
| outputs: | |
| semver: ${{ steps.version.outputs.semver }} | |
| assembly_ver: ${{ steps.version.outputs.assembly_ver }} | |
| tag: ${{ steps.version.outputs.tag }} | |
| zip_name: ${{ steps.version.outputs.zip_name }} | |
| steps: | |
| - name: Set version outputs | |
| id: version | |
| shell: bash | |
| run: | | |
| SEMVER="${ENHANCED_VERSION_BASE}.${{ github.run_number }}" | |
| # AssemblyVersion/FileVersion must be numeric (no pre-release label). | |
| ASSEMBLY_VER="0.0.1.${{ github.run_number }}" | |
| TAG="enhanced-v${SEMVER}" | |
| ZIP_NAME="vMenu-Enhanced-v${SEMVER}.zip" | |
| echo "semver=$SEMVER" >> "$GITHUB_OUTPUT" | |
| echo "assembly_ver=$ASSEMBLY_VER" >> "$GITHUB_OUTPUT" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| echo "zip_name=$ZIP_NAME" >> "$GITHUB_OUTPUT" | |
| # ============================================================ | |
| # BUILD - runs on enhanced pushes and on pull requests | |
| # ============================================================ | |
| build: | |
| name: Build | |
| needs: version | |
| runs-on: windows-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| global-json-file: global.json | |
| - name: Restore | |
| run: dotnet restore vMenu.Enhanced.slnx | |
| - name: Build | |
| shell: bash | |
| run: | | |
| dotnet build vMenu.Enhanced.slnx -c Release --no-restore \ | |
| -p:Version="${{ needs.version.outputs.semver }}" \ | |
| -p:AssemblyVersion="${{ needs.version.outputs.assembly_ver }}" \ | |
| -p:FileVersion="${{ needs.version.outputs.assembly_ver }}" | |
| # The plugin API packages ship to nuget.org with the exact same version as | |
| # the resource zip, so a plugin author pins the package matching whatever | |
| # vMenu version their server runs. | |
| - name: Pack plugin API packages | |
| shell: bash | |
| run: | | |
| for project in vMenu.Enhanced.PluginContracts vMenu.Enhanced.ClientAPI vMenu.Enhanced.ServerAPI; do | |
| dotnet pack "src/PluginApi/$project/$project.csproj" -c Release --no-build \ | |
| -p:Version="${{ needs.version.outputs.semver }}" \ | |
| -p:PackageVersion="${{ needs.version.outputs.semver }}" \ | |
| -o packages | |
| done | |
| - name: Upload package artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: vmenu-enhanced-packages | |
| path: packages/*.nupkg | |
| # The build works out how much of the English string table each language | |
| # covers and drops it in build/intermediate (see GenerateLanguageTemplate | |
| # in src/Directory.Build.targets). Surfaced here so a PR that adds English | |
| # strings without translations is visible without reading the build log. | |
| - name: Translation coverage | |
| if: always() | |
| shell: pwsh | |
| run: | | |
| $path = 'build/intermediate/translation-coverage.json' | |
| if (-not (Test-Path $path)) { | |
| Write-Host 'No translation coverage was produced by this build.' | |
| exit 0 | |
| } | |
| $report = Get-Content $path -Raw | ConvertFrom-Json | |
| $total = $report.totalKeys | |
| $behind = @($report.languages | Where-Object { $_.missing -gt 0 -or $_.orphans -gt 0 -or $_.unreadable }) | |
| $lines = @() | |
| $lines += "## Translation coverage" | |
| $lines += "" | |
| $lines += if ($behind.Count -eq 0) { | |
| "All languages cover every one of the $total English keys." | |
| } else { | |
| "$($behind.Count) of $($report.languages.Count) languages are behind the $total English keys." | |
| } | |
| $lines += "" | |
| $lines += "| Language | Translated | Missing | Stale keys |" | |
| $lines += "| --- | ---: | ---: | ---: |" | |
| foreach ($language in $report.languages) { | |
| $name = "$($language.nativeName) (``$($language.code)``)" | |
| if ($language.unreadable) { | |
| $lines += "| $name | file could not be read | | |" | |
| continue | |
| } | |
| $mark = if ($language.missing -eq 0) { '0' } else { "**$($language.missing)**" } | |
| $lines += "| $name | $($language.translated)/$total | $mark | $($language.orphans) |" | |
| } | |
| # The names, not just the counts, so a translator can act on this | |
| # without checking the repo out. Run `dotnet run --project | |
| # tools/LanguageTemplate -- check` locally for the same thing. | |
| $shown = 25 | |
| foreach ($language in $behind) { | |
| if ($language.unreadable) { continue } | |
| $missing = @($language.missingKeys) | |
| $orphans = @($language.orphanKeys) | |
| if ($missing.Count -eq 0 -and $orphans.Count -eq 0) { continue } | |
| $lines += "" | |
| $lines += "<details><summary>$($language.nativeName) (``$($language.code)``)</summary>" | |
| $lines += "" | |
| foreach ($set in @( | |
| @{ Label = 'Missing'; Keys = $missing }, | |
| @{ Label = 'No longer in English'; Keys = $orphans })) { | |
| if ($set.Keys.Count -eq 0) { continue } | |
| $lines += "$($set.Label) ($($set.Keys.Count)):" | |
| $lines += "" | |
| $lines += '```' | |
| $lines += $set.Keys | Select-Object -First $shown | |
| if ($set.Keys.Count -gt $shown) { | |
| $lines += "... and $($set.Keys.Count - $shown) more" | |
| } | |
| $lines += '```' | |
| $lines += "" | |
| } | |
| $lines += "</details>" | |
| } | |
| $lines += "" | |
| $lines += "_Missing keys fall back to English, so nothing is broken by a gap here._" | |
| $summary = $lines -join "`n" | |
| $summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append -Encoding utf8 | |
| # Kept for the comment step below, so the PR conversation and the check | |
| # summary cannot drift into saying different things. | |
| $summary | Out-File -FilePath 'build/intermediate/translation-coverage.md' -Encoding utf8 | |
| Write-Host $summary | |
| # Posted as a comment as well, so it shows up in the PR conversation | |
| # rather than only under the check. Skipped for pull requests from forks, | |
| # whose token cannot write comments. | |
| - name: Comment translation coverage on the PR | |
| if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| shell: pwsh | |
| run: | | |
| $path = 'build/intermediate/translation-coverage.md' | |
| if (-not (Test-Path $path)) { | |
| exit 0 | |
| } | |
| # A marker so each run edits its own comment instead of adding another. | |
| $marker = '<!-- vmenu-translation-coverage -->' | |
| $body = $marker + "`n" + (Get-Content $path -Raw) | |
| $file = New-TemporaryFile | |
| $body | Out-File -FilePath $file -Encoding utf8 | |
| $existing = gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" --paginate | | |
| ConvertFrom-Json | | |
| Where-Object { $_.body -like "*$marker*" } | | |
| Select-Object -First 1 | |
| if ($existing) { | |
| gh api --method PATCH "repos/${{ github.repository }}/issues/comments/$($existing.id)" -F "body=@$file" | |
| } else { | |
| gh api --method POST "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" -F "body=@$file" | |
| } | |
| # fxmanifest.lua, README.md and LICENSE.md are copied into the resource | |
| # root by the build itself (CopyResourceRootFiles, see | |
| # src/Directory.Build.targets), so only the version stamp is left to do. | |
| - name: Inject version into fxmanifest.lua | |
| shell: pwsh | |
| run: | | |
| (Get-Content 'build/vMenu.Enhanced/fxmanifest.lua') -replace 'versiongoeshere', '${{ needs.version.outputs.semver }}' | | |
| Set-Content -Encoding ASCII 'build/vMenu.Enhanced/fxmanifest.lua' | |
| - name: Package zip | |
| shell: pwsh | |
| run: Compress-Archive -Path 'build/vMenu.Enhanced/*' -DestinationPath '${{ needs.version.outputs.zip_name }}' -Force | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: vmenu-enhanced-zip | |
| path: ${{ needs.version.outputs.zip_name }} | |
| # ============================================================ | |
| # RELEASE - enhanced branch only, always a DRAFT PRE-RELEASE while alpha | |
| # ============================================================ | |
| release: | |
| name: Release (draft pre-release) | |
| needs: [version, build] | |
| if: github.ref == 'refs/heads/enhanced' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| release_url: ${{ steps.create_release.outputs.release_url }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: vmenu-enhanced-zip | |
| path: release-assets | |
| # Changelog spans commits since the previous enhanced-v pre-release tag. | |
| # Note: draft releases don't create their tag until published, so early on | |
| # there may be no enhanced-v tags yet and we fall back to the full log. | |
| - name: Generate changelog | |
| shell: bash | |
| run: | | |
| LAST_TAG=$(git tag --sort=-version:refname | grep '^enhanced-v' | head -n 1 || true) | |
| if [ -n "$LAST_TAG" ]; then | |
| git log "$LAST_TAG..HEAD" --pretty=format:'- `%h` (%an) %s' > changelog.txt | |
| else | |
| git log --pretty=format:'- `%h` (%an) %s' > changelog.txt | |
| fi | |
| echo "Changelog since: ${LAST_TAG:-<beginning>}" | |
| cat changelog.txt | |
| - name: Create GitHub Release (draft pre-release) | |
| id: create_release | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.version.outputs.semver }}" | |
| TAG="${{ needs.version.outputs.tag }}" | |
| { | |
| echo "vMenu Enhanced pre-release v${VERSION} (alpha)." | |
| echo "" | |
| echo "> [!WARNING]" | |
| echo "> This is an early alpha build of vMenu for FiveM Enhanced. Expect breaking changes and incomplete features." | |
| echo "" | |
| echo "<details><summary>Changes since the last Enhanced pre-release</summary>" | |
| echo "" | |
| cat changelog.txt | |
| echo "" | |
| echo "</details>" | |
| } > release_notes.md | |
| # --draft keeps the release (and its tag) unpublished until reviewed: | |
| # the enhanced-v tag is only created on the target commit when the | |
| # draft is published, so alpha builds don't spam tags. | |
| RELEASE_URL=$(gh release create "$TAG" \ | |
| --title "vMenu Enhanced v${VERSION}" \ | |
| --notes-file release_notes.md \ | |
| --prerelease --draft \ | |
| --target "$GITHUB_SHA" \ | |
| "release-assets/${{ needs.version.outputs.zip_name }}") | |
| echo "release_url=$RELEASE_URL" >> "$GITHUB_OUTPUT" | |
| # ============================================================ | |
| # NUGET - publishes the plugin API packages, enhanced branch only | |
| # ============================================================ | |
| publish-nuget: | |
| name: Publish NuGet packages | |
| needs: [version, build] | |
| if: github.ref == 'refs/heads/enhanced' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| id-token: write | |
| steps: | |
| - name: Download package artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: vmenu-enhanced-packages | |
| path: packages | |
| - name: NuGet login (trusted publishing) | |
| id: login | |
| uses: NuGet/login@v1 | |
| with: | |
| user: TomGrobbe | |
| - name: Push packages | |
| shell: bash | |
| run: | | |
| dotnet nuget push "packages/*.nupkg" \ | |
| --source https://api.nuget.org/v3/index.json \ | |
| --api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \ | |
| --skip-duplicate | |
| # ============================================================ | |
| # DISCORD NOTIFICATION | |
| # ============================================================ | |
| # notify: | |
| # name: Discord Notification | |
| # needs: [version, build, release] | |
| # if: always() | |
| # runs-on: ubuntu-latest | |
| # steps: | |
| # - name: Send Discord notification | |
| # shell: bash | |
| # env: | |
| # DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} | |
| # run: | | |
| # if [ -z "${DISCORD_WEBHOOK_URL:-}" ]; then | |
| # echo "DISCORD_WEBHOOK_URL not configured, skipping." | |
| # exit 0 | |
| # fi | |
| # BUILD_RESULT="${{ needs.build.result }}" | |
| # RELEASE_RESULT="${{ needs.release.result }}" | |
| # VERSION="${{ needs.version.outputs.semver }}" | |
| # if [ "$BUILD_RESULT" = "cancelled" ] || [ "$RELEASE_RESULT" = "cancelled" ]; then | |
| # COLOR=16776960; STATUS_TEXT="Build cancelled." | |
| # elif [ "$BUILD_RESULT" = "success" ] && { [ "$RELEASE_RESULT" = "success" ] || [ "$RELEASE_RESULT" = "skipped" ]; }; then | |
| # COLOR=4502298; STATUS_TEXT="Build passed!" | |
| # else | |
| # COLOR=13632027; STATUS_TEXT="Build FAILED!" | |
| # fi | |
| # RELEASE_URL="${{ needs.release.outputs.release_url }}" | |
| # if [ -n "$RELEASE_URL" ] && [ "$RELEASE_RESULT" = "success" ]; then | |
| # LINK_URL="$RELEASE_URL" | |
| # else | |
| # LINK_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| # fi | |
| # PAYLOAD=$(jq -n \ | |
| # --arg title "vMenu Enhanced (v${VERSION})" \ | |
| # --arg desc "$STATUS_TEXT" \ | |
| # --arg url "$LINK_URL" \ | |
| # --argjson color "$COLOR" \ | |
| # '{embeds:[{title:$title,description:$desc,url:$url,color:$color}]}') | |
| # curl -fsSL -H "Content-Type: application/json" -X POST -d "$PAYLOAD" "$DISCORD_WEBHOOK_URL" |