CI/CD Pipeline
MG-CLI is not only a tool for CI/CD — it also has its own CI/CD that publishes each release to NuGet automatically. This page documents that self-publishing pipeline, and then discusses how to use MG-CLI inside your game's pipeline.
The tool's own pipeline
The workflow lives at .github/workflows/publish.yml and runs on every push to main, except when the commit message contains [skip ci]:
on:
push:
branches: [main]
jobs:
publish:
if: "!contains(github.event.head_commit.message, '[skip ci]')"
What it does, step by step
- Checkout & setup .NET 10.
- Restore dependencies (
dotnet restore). - Bump the version by running the tool on itself:
This increments the patch segment ofdotnet run --project MG-CLI/MG-CLI.csproj csproj-versioning MG-CLI/MG-CLI.csproj Version<Version>inMG-CLI.csproj(see Versioning). - Read the new version back out of the
.csprojinto a step output. - Commit & tag the bump as
github-actions[bot]:
Thegit commit -m "chore: bump version to <ver> [skip ci]" git tag "v<ver>" git push origin main --tags[skip ci]marker prevents this bot commit from re-triggering the workflow — an infinite loop guard. - Build in Release (
dotnet build --configuration Release --no-restore). - Pack the NuGet package with the computed version (
dotnet pack … -p:Version=<ver>). - Push to NuGet (
dotnet nuget push … --skip-duplicate) using theNUGET_API_KEYsecret.
The [skip ci] loop guard
This pattern is worth calling out because it is easy to get wrong. The workflow itself pushes a commit (the version bump). Without a guard, that push would trigger the workflow again, which would bump again, and so on. The [skip ci] string in the bot's commit message, combined with the if: condition on the job, breaks the cycle. You can see the guard commits in the repo history as chore: bump version to X.Y.Z [skip ci].
Required configuration
To run this pipeline in a fork or your own repo:
- Add a repository secret named
NUGET_API_KEY(Settings → Secrets and variables → Actions) with a valid nuget.org API key. - Ensure the workflow has
contents: writepermission (already declared in the workflow) so it can push the bump commit and tag.
Using MG-CLI in your game's pipeline
MG-CLI's commands are designed to be dropped into CI steps. Because each returns a meaningful exit code, a failing build or deploy fails the pipeline.
A GitHub Actions sketch for a Godot game might look like:
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 } # full history + tags, so discord-hook can diff
- uses: actions/setup-dotnet@v4
with: { dotnet-version: "10.0.x" }
- name: Install MG-CLI
run: dotnet tool install --global mg-cli
- name: Install Godot
run: mg-cli godot install 4.4.1
- name: Bump version
run: (cd game && mg-cli godot version --bump)
- name: Build
run: mg-cli godot build -p ./game -v 4.4.1 -r Linux
- name: Deploy to Steam
run: mg-cli steam deploy -p ./game --vdf ./steam/app_build.vdf -u "${{ secrets.STEAM_USER }}" -pw "${{ secrets.STEAM_PASS }}"
- name: Tag & push
run: mg-cli commit ./game
- name: Announce on Discord
run: mg-cli discord-hook -p ./game -h "${{ secrets.DISCORD_HOOK }}" -s "$STEAM_URL" -l "$LOGO_URL"
Tips for CI use
- Fetch full history and tags (
fetch-depth: 0).discord-hookdiffs between the two most recent tags to build a changelog, andcommitpushes tags — both need the tags present. - Pass credentials as secrets, never inline.
steam deploytakes username/password on the command line; source them from masked CI secrets/environment variables. - Watch for interactive prompts.
itchio setuprunsbutler logininteractively andsteam deploypipes stdin through for Steam Guard — plan authentication (persisted Butler creds, Steam Guard handling) so unattended runs do not block. - Cache the Godot install where possible;
godot installre-downloads the engine and templates each time otherwise. - Rely on exit codes. Keep steps separate (as above) or chain with
&&so a failure stops the pipeline before it deploys a bad build.
See Getting Started for the equivalent commands run locally, and the Command Reference for every flag.