diff --git a/.github/workflows/build-and-push-docker.yml b/.github/workflows/build-and-push-docker.yml index e4bffabe..b7ae1d06 100644 --- a/.github/workflows/build-and-push-docker.yml +++ b/.github/workflows/build-and-push-docker.yml @@ -1,14 +1,14 @@ -name: CI +name: Docker on: push: branches: - - "main" + - main tags: - "v*.*.*" pull_request: branches: - - "main" + - main env: REGISTRY: ghcr.io @@ -20,15 +20,21 @@ jobs: permissions: contents: read packages: write + attestations: write + id-token: write steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: recursive + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 + - name: Log in to the Container registry - uses: docker/login-action@v3 + if: github.event_name != 'pull_request' + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -36,21 +42,32 @@ jobs: - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - # generate Docker tags based on the following events/attributes tags: | type=ref,event=pr type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} + type=raw,value=canary,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} - name: Build and push Docker image - uses: docker/build-push-action@v5 + id: push + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Sign Docker image + if: github.event_name != 'pull_request' + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index f0806156..c26f0dee 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -3,6 +3,8 @@ name: CI on: push: branches: [ main ] + tags: + - "v*.*.*" pull_request: branches: [ main ] @@ -10,38 +12,51 @@ jobs: build-and-test: name: Build & Test (${{ matrix.os }}) runs-on: ${{ matrix.os }} - continue-on-error: true + continue-on-error: ${{ github.event_name == 'pull_request' }} strategy: matrix: - os: [ windows-2022, ubuntu-22.04, macos-15-intel ] + include: + - os: windows-2025 + artifact: windows + debug_preset: windows-msvc-relwithdebinfo + - os: ubuntu-24.04 + artifact: linux + debug_preset: linux-gnu-relwithdebinfo + - os: macos-15-intel + artifact: macos + debug_preset: macos-relwithdebinfo steps: - - uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - name: Add msbuild to PATH (Windows only) - if: ${{ matrix.os == 'windows-2022' }} - uses: microsoft/setup-msbuild@767f00a3f09872d96a0cb9fcd5e6a4ff33311330 + if: ${{ matrix.os == 'windows-2025' }} + uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3 with: vs-version: '[17,18)' msbuild-architecture: x64 - - name: Install libssl and switch to XCode 15.2 (Mac Only) - if: ${{ matrix.os == 'macos-13' }} - run: | - brew install openssl@3 - sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer - name: Get CMake 3.x - uses: lukka/get-cmake@28983e0d3955dba2bb0a6810caae0c6cf268ec0c + uses: lukka/get-cmake@591817e96fcad43505fb4eae36172462abb3a42e # v4.3.3 with: - cmakeVersion: "~3.25.0" # <--= optional, use most recent 3.25.x version + cmakeVersion: "~3.25.0" - name: cmake - uses: lukka/run-cmake@67c73a83a46f86c4e0b96b741ac37ff495478c38 + uses: lukka/run-cmake@5d55ea7949e25f69f0ecb516d8d572297e03a956 # v10.9 with: - workflowPreset: "ci-${{matrix.os}}" + workflowPreset: "${{ matrix.debug_preset }}" + + - name: Extract Linux debug symbols + if: matrix.os == 'ubuntu-24.04' + run: | + find build -type f -name '*Server' | while read bin; do + objcopy --only-keep-debug "$bin" "${bin}.debug" + objcopy --strip-debug --add-gnu-debuglink="${bin}.debug" "$bin" + done + - name: artifacts - uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: build-${{matrix.os}} + name: build-${{matrix.artifact}} path: | build/*/*Server* build/*/*.ini @@ -52,5 +67,30 @@ jobs: build/*/navmeshes/ build/*/migrations/ build/*/*.dcf - !build/*/*.pdb !build/*/d*/ + !build/*/*.dSYM/ + !build/**/*.debug + + - name: debug symbols (Windows) + if: matrix.os == 'windows-2025' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: debug-${{matrix.artifact}} + path: | + build/*/*.pdb + build/*/d*/ + retention-days: 30 + - name: debug symbols (Linux) + if: matrix.os == 'ubuntu-24.04' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: debug-${{matrix.artifact}} + path: build/**/*.debug + retention-days: 30 + - name: debug symbols (macOS) + if: matrix.os == 'macos-15-intel' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: debug-${{matrix.artifact}} + path: build/**/*.dSYM/ + retention-days: 30 diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml new file mode 100644 index 00000000..2a3a470b --- /dev/null +++ b/.github/workflows/canary.yml @@ -0,0 +1,93 @@ +name: Canary + +on: + workflow_run: + workflows: ["CI"] + branches: [main] + types: [completed] + +permissions: + contents: write + actions: read + +jobs: + canary-release: + name: Publish Canary Release + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + ref: ${{ github.event.workflow_run.head_sha }} + + - name: Get last release tag + id: last_tag + run: | + tag=$(git describe --tags --abbrev=0 --match "v*.*.*" 2>/dev/null || echo "none") + echo "tag=$tag" >> "$GITHUB_OUTPUT" + + - name: Generate changelog since last release tag + uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4.8.0 + id: cliff + with: + config: cliff.toml + args: --unreleased --strip header + env: + OUTPUT: CHANGES.md + GITHUB_REPO: ${{ github.repository }} + + - name: Prepend header to changelog + run: | + last="${{ steps.last_tag.outputs.tag }}" + sha="${{ github.event.workflow_run.head_sha }}" + short="${sha:0:7}" + if [ "$last" != "none" ]; then + header="Changes since **$last** ([full diff](https://github.com/${{ github.repository }}/compare/${last}...${sha}))\n\n" + else + header="Changes up to \`${short}\`\n\n" + fi + printf "%b" "$header" | cat - CHANGES.md > CHANGES.tmp && mv CHANGES.tmp CHANGES.md + + - name: Download artifacts from CI run + uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21 + with: + run_id: ${{ github.event.workflow_run.id }} + path: artifacts/ + + - name: Package artifacts + run: | + declare -A platform_map=( + ["build-windows"]="darkflame-universe-windows" + ["build-linux"]="darkflame-universe-linux" + ["build-macos"]="darkflame-universe-macos" + ) + cd artifacts + for dir in build-*/; do + name="${dir%/}" + out="${platform_map[$name]:-$name}" + zip -r "../${out}.zip" "$dir" + done + cd .. + ls -lh *.zip + + - name: Delete existing canary release + run: gh release delete canary --yes --cleanup-tag 2>/dev/null || true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create canary pre-release + uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0 + with: + tag: canary + name: "Canary ${{ steps.last_tag.outputs.tag }}+${{ github.event.workflow_run.head_sha }}" + bodyFile: CHANGES.md + artifacts: "*.zip" + artifactContentType: application/zip + token: ${{ secrets.GITHUB_TOKEN }} + prerelease: true + draft: false + allowUpdates: true + removeArtifacts: true + commit: ${{ github.event.workflow_run.head_sha }} diff --git a/.github/workflows/pr-title-check.yml b/.github/workflows/pr-title-check.yml new file mode 100644 index 00000000..00ddf4ca --- /dev/null +++ b/.github/workflows/pr-title-check.yml @@ -0,0 +1,37 @@ +name: PR Title Check + +on: + pull_request_target: + types: [opened, edited, synchronize, reopened] + +permissions: + pull-requests: write + +jobs: + check-title: + name: Conventional Commit Title + runs-on: ubuntu-latest + + steps: + - name: Check PR title follows Conventional Commits + uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + types: | + feat + fix + perf + refactor + docs + chore + test + ci + revert + requireScope: false + subjectPattern: ^.+$ + subjectPatternError: | + The PR title "{title}" must have a description after the type/scope prefix. + Example: "feat: add new login flow" or "fix(auth): handle null token" + wip: true + validateSingleCommit: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..b634b90b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,70 @@ +name: Release + +on: + workflow_run: + workflows: ["CI"] + types: [completed] + +permissions: + contents: write + actions: read + +jobs: + release: + name: Create Release + # Only run when CI completed successfully on a tag push (not a PR branch named like a version) + if: | + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + startsWith(github.event.workflow_run.head_branch, 'v') + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + ref: ${{ github.event.workflow_run.head_sha }} + + - name: Generate changelog + uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4.8.0 + id: cliff + with: + config: cliff.toml + args: --latest --strip header + env: + OUTPUT: CHANGES.md + GITHUB_REPO: ${{ github.repository }} + + - name: Download artifacts from CI run + uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21 + with: + run_id: ${{ github.event.workflow_run.id }} + path: artifacts/ + + - name: Package artifacts + run: | + declare -A platform_map=( + ["build-windows"]="darkflame-universe-windows" + ["build-linux"]="darkflame-universe-linux" + ["build-macos"]="darkflame-universe-macos" + ) + cd artifacts + for dir in build-*/; do + name="${dir%/}" + out="${platform_map[$name]:-$name}" + zip -r "../${out}.zip" "$dir" + done + cd .. + ls -lh *.zip + + - name: Create GitHub Release + uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0 + with: + tag: ${{ github.event.workflow_run.head_branch }} + name: ${{ github.event.workflow_run.head_branch }} + bodyFile: CHANGES.md + artifacts: "*.zip" + artifactContentType: application/zip + token: ${{ secrets.GITHUB_TOKEN }} + prerelease: false + draft: false diff --git a/.gitignore b/.gitignore index d7af5d1f..b1a336af 100644 --- a/.gitignore +++ b/.gitignore @@ -126,3 +126,5 @@ docker-compose.override.yml # CMake scripts !cmake/* !cmake/toolchains/* +.mcp.json +.claude/ diff --git a/CMakePresets.json b/CMakePresets.json index 9806a3a3..2819f320 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -162,6 +162,13 @@ "rhs": "Darwin" }, "binaryDir": "${sourceDir}/build/macos" + }, + { + "name": "macos-relwithdebinfo", + "inherits": ["macos", "relwithdebinfo-config"], + "displayName": "[RelWithDebInfo] MacOS", + "description": "Create a RelWithDebInfo build for MacOS", + "binaryDir": "${sourceDir}/build/macos-relwithdebinfo" } ], "buildPresets": [ @@ -255,7 +262,7 @@ { "name": "macos-relwithdebinfo", "inherits": "default", - "configurePreset": "macos", + "configurePreset": "macos-relwithdebinfo", "displayName": "[RelWithDebInfo] MacOS", "description": "This preset is used to build in release mode with debug info on MacOS", "configuration": "RelWithDebInfo" @@ -374,7 +381,7 @@ { "name": "macos-relwithdebinfo", "inherits": "default", - "configurePreset": "macos", + "configurePreset": "macos-relwithdebinfo", "displayName": "[RelWithDebInfo] MacOS", "description": "Runs all tests on a MacOS configuration", "configuration": "RelWithDebInfo" @@ -603,7 +610,7 @@ "steps": [ { "type": "configure", - "name": "macos" + "name": "macos-relwithdebinfo" }, { "type": "build", diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 00000000..18b2597e --- /dev/null +++ b/cliff.toml @@ -0,0 +1,35 @@ +[changelog] +header = "" +body = """ +{% for group, commits in commits | group_by(attribute="group") %} +### {{ group | upper_first }} +{% for commit in commits %} +- {% if commit.scope %}**{{ commit.scope }}**: {% endif %}{{ commit.message }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}/commit/{{ commit.id }}))\ +{% if commit.github.username %} by [@{{ commit.github.username }}](https://github.com/{{ commit.github.username }}){% endif %} +{% endfor %} +{% endfor %} +""" +footer = "" +trim = true + +[git] +conventional_commits = true +filter_unconventional = true +split_commits = false +commit_parsers = [ + { message = "^feat", group = "Features" }, + { message = "^fix", group = "Bug Fixes" }, + { message = "^perf", group = "Performance" }, + { message = "^refactor", group = "Refactoring" }, + { message = "^docs", group = "Documentation" }, + { message = "^chore", group = "Chores" }, + { message = "^test", group = "Testing" }, + { message = "^ci", group = "CI/CD" }, + { message = "^revert", group = "Reverts" }, +] +filter_commits = false +tag_pattern = "v[0-9].*" +skip_tags = "canary" +ignore_tags = "" +topo_order = false +sort_commits = "oldest"