Skip to content

fix(vehicles): replace the personal vehicle when the server forces re… #105

fix(vehicles): replace the personal vehicle when the server forces re…

fix(vehicles): replace the personal vehicle when the server forces re… #105

Workflow file for this run

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"