Skip to content

feat: improve DMF setup and launch feedback (#195) #95

feat: improve DMF setup and launch feedback (#195)

feat: improve DMF setup and launch feedback (#195) #95

Workflow file for this run

# Modificus Curator: release-please automation + release asset build/attestation.
#
# Triggered on push to main and via workflow_dispatch. Cuts release PRs,
# publishes releases, builds Windows and Linux x64 archives (app/ + relay/),
# attaches assets to the release, attests them with Sigstore, and dispatches
# to the post-release AV/VT workflow.
#
# Uses in-workflow chaining (build/attest after release-please) rather than
# on:release because events created with GITHUB_TOKEN do not trigger other workflows.
name: release
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
release-please:
runs-on: ubuntu-latest
outputs:
releases_created: ${{ steps.release.outputs.releases_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
version: ${{ steps.release.outputs.version }}
sha: ${{ steps.release.outputs.sha }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: googleapis/release-please-action@v5
id: release
with:
config-file: .release-please-config.json
manifest-file: .release-please-manifest.json
build-windows:
needs: release-please
if: ${{ needs.release-please.outputs.releases_created == 'true' }}
runs-on: windows-latest
permissions:
contents: write
id-token: write
attestations: write
defaults:
run:
working-directory: src
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.release-please.outputs.sha }}
- name: Setup .NET 10
uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'
- name: Cache NuGet packages
uses: actions/cache@v6
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.sln') }}
restore-keys: |
nuget-${{ runner.os }}-
- name: Restore
run: dotnet restore modificus-curator.sln
- name: Build (Release)
run: dotnet build modificus-curator.sln --configuration Release --no-restore
- name: Publish Curator UI (framework-dependent)
shell: bash
run: |
version="${{ needs.release-please.outputs.version }}"
version="${version#v}"
dotnet publish ui --configuration Release --runtime win-x64 --self-contained false --output stage/app \
-p:Version="$version" -p:InformationalVersion="$version" -p:CuratorUseVelopack=true
- name: Publish NXM handler (AOT win-x64)
run: dotnet publish nxm-handler --configuration Release --runtime win-x64 --output stage/app
- name: Fetch latest Relay release
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RELAY_TAG=$(gh release list --repo ModifAmorphic/darktide-mod-relay \
--limit 100 --exclude-drafts --order desc --json tagName,isPrerelease,publishedAt \
--jq '[.[] | select(.isPrerelease == false)] | .[0].tagName // empty')
if [ -z "$RELAY_TAG" ]; then
echo "::error::No non-draft Relay release found"
exit 1
fi
echo "RELAY_TAG=$RELAY_TAG" >> "$GITHUB_ENV"
echo "Relay tag: $RELAY_TAG"
gh release download "$RELAY_TAG" --repo ModifAmorphic/darktide-mod-relay \
--pattern "*-windows-x64.zip" --dir stage
- name: Extract Relay for portable (top-level relay/)
shell: bash
run: |
mkdir -p stage/relay
unzip -q stage/*-windows-x64.zip -d stage/relay
- name: Extract Relay for Velopack (app/relay/)
shell: bash
run: |
mkdir -p stage/app/relay
unzip -q stage/*-windows-x64.zip -d stage/app/relay
rm stage/*-windows-x64.zip
- name: Resolve version
id: rel
shell: bash
run: |
version="${{ needs.release-please.outputs.version }}"
version="${version#v}"
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Publish Curator UI (portable, no Velopack)
shell: bash
run: |
version="${{ needs.release-please.outputs.version }}"
version="${version#v}"
dotnet publish ui --configuration Release --runtime win-x64 --self-contained false --output stage-portable/app \
-p:Version="$version" -p:InformationalVersion="$version"
- name: Publish NXM handler (portable, AOT win-x64)
run: dotnet publish nxm-handler --configuration Release --runtime win-x64 --output stage-portable/app
- name: Stage relay for portable archive
shell: bash
run: |
cp -r stage/relay stage-portable/relay
- name: Create portable Windows archive
shell: bash
run: |
cd stage-portable
powershell -Command "Compress-Archive -Path app, relay -DestinationPath 'curator-${{ needs.release-please.outputs.tag_name }}-windows-x64.zip'"
- name: Upload portable Windows archive
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
gh release upload "${{ needs.release-please.outputs.tag_name }}" \
"stage-portable/curator-${{ needs.release-please.outputs.tag_name }}-windows-x64.zip" --clobber
- name: Attest portable Windows archive
uses: actions/attest@v4
with:
subject-path: src/stage-portable/curator-${{ needs.release-please.outputs.tag_name }}-windows-x64.zip
- name: Pack Velopack installer
shell: bash
run: |
# Version-pinned vpk install (local path, no global tool manifest).
dotnet tool install vpk --version 1.2.0 --tool-path .vpk-temp
.vpk-temp/vpk pack \
--packId ModifAmorphic.ModificusCurator \
--packVersion "${{ steps.rel.outputs.version }}" \
--packDir stage/app \
--mainExe Modificus.Curator.exe \
--packTitle "Modificus Curator" \
--icon ui/app-icon.ico \
--framework net10.0-x64-runtime \
-o stage/releases
- name: Rename Setup.exe
shell: bash
run: |
mv stage/releases/ModifAmorphic.ModificusCurator-win-Setup.exe stage/releases/modificus-curator-setup.exe
- name: Upload Velopack assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
version="${{ steps.rel.outputs.version }}"
gh release upload "${{ needs.release-please.outputs.tag_name }}" \
stage/releases/modificus-curator-setup.exe \
"stage/releases/ModifAmorphic.ModificusCurator-${version}-full.nupkg" \
stage/releases/releases.win.json \
--clobber
# Upload delta.nupkg if present (will exist for non-first releases)
if [ -f "stage/releases/ModifAmorphic.ModificusCurator-${version}-delta.nupkg" ]; then
gh release upload "${{ needs.release-please.outputs.tag_name }}" \
"stage/releases/ModifAmorphic.ModificusCurator-${version}-delta.nupkg" --clobber
fi
- name: Attest Velopack installer
uses: actions/attest@v4
with:
subject-path: src/stage/releases/modificus-curator-setup.exe
- name: Attest Velopack nupkg
uses: actions/attest@v4
with:
subject-path: src/stage/releases/ModifAmorphic.ModificusCurator-${{ steps.rel.outputs.version }}-full.nupkg
build-linux:
needs: release-please
if: ${{ needs.release-please.outputs.releases_created == 'true' }}
runs-on: ubuntu-22.04
permissions:
contents: write
id-token: write
attestations: write
defaults:
run:
working-directory: src
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.release-please.outputs.sha }}
- name: Setup .NET 10
uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'
- name: Install native AOT prerequisites (clang, zlib)
run: |
sudo apt-get update
sudo apt-get install -y clang zlib1g-dev squashfs-tools
- name: Cache NuGet packages
uses: actions/cache@v6
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.sln') }}
restore-keys: |
nuget-${{ runner.os }}-
- name: Restore
run: dotnet restore modificus-curator.sln
- name: Build (Release)
run: dotnet build modificus-curator.sln --configuration Release --no-restore
- name: Publish Curator UI (framework-dependent, standalone)
run: |
VERSION="${{ needs.release-please.outputs.version }}"
VERSION="${VERSION#v}"
dotnet publish ui --configuration Release --runtime linux-x64 --self-contained false --output stage/app \
-p:Version="$VERSION" -p:InformationalVersion="$VERSION"
- name: Publish NXM handler (AOT linux-x64, standalone)
run: dotnet publish nxm-handler --configuration Release --runtime linux-x64 --output stage/app
- name: Fetch latest Relay release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
RELAY_TAG=$(gh release list --repo ModifAmorphic/darktide-mod-relay \
--limit 100 --exclude-drafts --order desc --json tagName,isPrerelease,publishedAt \
--jq '[.[] | select(.isPrerelease == false)] | .[0].tagName // empty')
if [ -z "$RELAY_TAG" ]; then
echo "::error::No non-draft Relay release found"
exit 1
fi
echo "RELAY_TAG=$RELAY_TAG" >> "$GITHUB_ENV"
echo "Relay tag: $RELAY_TAG"
gh release download "$RELAY_TAG" --repo ModifAmorphic/darktide-mod-relay \
--pattern "*-windows-x64.zip" --dir stage
- name: Extract Relay to relay/ directory (standalone)
run: |
mkdir -p stage/relay
unzip -q stage/*-windows-x64.zip -d stage/relay
- name: Create standalone release archive
run: |
tar czf "curator-${{ needs.release-please.outputs.tag_name }}-linux-x64.tar.gz" -C stage app relay
- name: Upload standalone release asset
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ needs.release-please.outputs.tag_name }}" \
"curator-${{ needs.release-please.outputs.tag_name }}-linux-x64.tar.gz" --clobber
- name: Attest standalone release asset
uses: actions/attest@v4
with:
subject-path: src/curator-${{ needs.release-please.outputs.tag_name }}-linux-x64.tar.gz
# AppImage packaging (separate from standalone)
- name: Publish Curator UI (AppImage, self-contained)
run: |
VERSION="${{ needs.release-please.outputs.version }}"
VERSION="${VERSION#v}"
dotnet publish ui --configuration Release --runtime linux-x64 --self-contained true --output stage/appimage \
-p:Version="$VERSION" -p:InformationalVersion="$VERSION" -p:CuratorUseVelopack=true
- name: Publish NXM handler (AOT linux-x64, AppImage)
run: dotnet publish nxm-handler --configuration Release --runtime linux-x64 --output stage/appimage
- name: Stage Relay for AppImage
run: |
mkdir -p stage/appimage/relay
unzip -q stage/*-windows-x64.zip -d stage/appimage/relay
rm stage/*-windows-x64.zip
- name: Seed prior Velopack feed and full package (for delta generation)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CURRENT_TAG: ${{ needs.release-please.outputs.tag_name }}
run: |
set -euo pipefail
mkdir -p stage/releases
# Query GitHub API for published releases (non-draft), considering both
# stable and prerelease. Exclude the current release. Require exact
# releases.linux-x64.json asset. Choose the newest prior published release.
PRIOR_RELEASE=$(gh api "repos/${GITHUB_REPOSITORY}/releases?per_page=100" | jq -r --arg current "$CURRENT_TAG" '[.[] | select(.draft == false) | select(.tag_name != $current) | select(any(.assets[]?; .name == "releases.linux-x64.json"))] | sort_by(.published_at) | reverse | .[0].tag_name // empty')
if [ -z "$PRIOR_RELEASE" ]; then
printf '%s\n' "No prior linux-x64 release with releases.linux-x64.json found; first AppImage release (no delta)"
exit 0
fi
printf '%s\n' "Seeding with prior release: $PRIOR_RELEASE"
# Download only the releases.linux-x64.json feed
gh release download "$PRIOR_RELEASE" --repo "${GITHUB_REPOSITORY}" \
--pattern "releases.linux-x64.json" --dir stage/releases
# Fetch the selected release JSON once and resolve the actual full-package asset name
RELEASE_JSON=$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${PRIOR_RELEASE}")
# Count assets ending with -linux-x64-full.nupkg and require exactly one
FULL_PKG_COUNT=$(printf '%s\n' "$RELEASE_JSON" | jq '[.assets[] | select(.name | test("-linux-x64-full\\.nupkg$"))] | length')
if [ "$FULL_PKG_COUNT" -ne 1 ]; then
printf '%s\n' "::error::Prior release $PRIOR_RELEASE has $FULL_PKG_COUNT matching full-package assets; expected exactly 1. Delta seeding failed."
exit 1
fi
# Extract the exact full-package filename (raw filename, not JSON array)
FULL_PKG_NAME=$(printf '%s\n' "$RELEASE_JSON" | jq -r '.assets[] | select(.name | test("-linux-x64-full\\.nupkg$")) | .name')
# Download the exact full package (never download delta or AppImage)
gh release download "$PRIOR_RELEASE" --repo "${GITHUB_REPOSITORY}" \
--pattern "$FULL_PKG_NAME" --dir stage/releases
printf '%s\n' "Seeded prior full package: $FULL_PKG_NAME"
- name: Install vpk 1.2.0 (job-local)
run: |
dotnet tool install vpk --version 1.2.0 --tool-path .vpk-temp
- name: Resolve version for vpk
id: rel
run: |
version="${{ needs.release-please.outputs.version }}"
version="${version#v}"
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Pack AppImage
run: |
.vpk-temp/vpk pack \
--packId ModifAmorphic.ModificusCurator \
--packVersion "${{ steps.rel.outputs.version }}" \
--packDir stage/appimage \
--mainExe Modificus.Curator \
--icon ui/Assets/app-icon.png \
--packTitle "Modificus Curator" \
--categories "Game;Utility" \
--channel linux-x64 \
--runtime linux-x64 \
--compression gzip \
-o stage/releases
- name: Rename AppImage to short asset name
run: |
cd stage/releases
mv ModifAmorphic.ModificusCurator-linux-x64.AppImage ModificusCurator-linux-x64.AppImage
[ -f ModifAmorphic.ModificusCurator-linux-x64.AppImage ] && { echo "FAIL: long AppImage still exists after rename"; exit 1; }
[ -f ModificusCurator-linux-x64.AppImage ] || { echo "FAIL: short AppImage does not exist after rename"; exit 1; }
[ -x ModificusCurator-linux-x64.AppImage ] || { echo "FAIL: short AppImage not executable after rename"; exit 1; }
echo "AppImage renamed successfully"
- name: Upload AppImage assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
version="${{ steps.rel.outputs.version }}"
gh release upload "${{ needs.release-please.outputs.tag_name }}" \
"stage/releases/ModificusCurator-linux-x64.AppImage" \
"stage/releases/ModifAmorphic.ModificusCurator-${version}-linux-x64-full.nupkg" \
stage/releases/releases.linux-x64.json \
--clobber
# Upload delta.nupkg if present (will exist for non-first releases)
if [ -f "stage/releases/ModifAmorphic.ModificusCurator-${version}-linux-x64-delta.nupkg" ]; then
gh release upload "${{ needs.release-please.outputs.tag_name }}" \
"stage/releases/ModifAmorphic.ModificusCurator-${version}-linux-x64-delta.nupkg" --clobber
fi
- name: Attest AppImage
uses: actions/attest@v4
with:
subject-path: src/stage/releases/ModificusCurator-linux-x64.AppImage
- name: Attest AppImage full nupkg
uses: actions/attest@v4
with:
subject-path: src/stage/releases/ModifAmorphic.ModificusCurator-${{ steps.rel.outputs.version }}-linux-x64-full.nupkg
- name: Attest AppImage delta nupkg (if present)
if: hashFiles('src/stage/releases/ModifAmorphic.ModificusCurator-*-linux-x64-delta.nupkg') != ''
uses: actions/attest@v4
with:
subject-path: src/stage/releases/ModifAmorphic.ModificusCurator-${{ steps.rel.outputs.version }}-linux-x64-delta.nupkg
dispatch-av-vt:
needs: [release-please, build-windows, build-linux]
if: ${{ always() && needs.release-please.result == 'success' && needs.build-windows.result == 'success' && needs.build-linux.result == 'success' && needs.release-please.outputs.releases_created == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Dispatch repository event for AV/VT scan
uses: actions/github-script@v9
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.repos.createDispatchEvent({
owner: context.repo.owner,
repo: context.repo.repo,
event_type: 'curator-release-assets-published',
client_payload: {
tag_name: '${{ needs.release-please.outputs.tag_name }}',
windows_asset: 'modificus-curator-setup.exe'
}
})
update-manifest:
needs: [release-please, build-linux]
if: ${{ needs.release-please.outputs.releases_created == 'true' && needs.build-linux.result == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout main
uses: actions/checkout@v7
- name: Resolve release metadata
id: rel
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.release-please.outputs.tag_name }}
run: |
prerelease=$(gh release view "$TAG" --json isPrerelease --jq '.isPrerelease' || echo true)
# Resolve standalone tarball by content type (existing behavior)
standalone_url=$(gh release view "$TAG" --json assets --jq '.assets[] | select(.contentType=="application/x-gtar") | .url' | head -n1)
if [ -z "$standalone_url" ]; then
echo "::error::No Linux tar.gz asset (contentType application/x-gtar) found in release $TAG"
exit 1
fi
# Resolve AppImage by exact asset name (not first asset/order/content type)
appimage_url=$(gh release view "$TAG" --json assets --jq '.assets[] | select(.name=="ModificusCurator-linux-x64.AppImage") | .url')
if [ -z "$appimage_url" ]; then
echo "::error::No Linux AppImage asset (ModificusCurator-linux-x64.AppImage) found in release $TAG"
exit 1
fi
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
echo "standalone_url=$standalone_url" >> "$GITHUB_OUTPUT"
echo "appimage_url=$appimage_url" >> "$GITHUB_OUTPUT"
- name: Update scripts/release.env
shell: bash
env:
PRERELEASE: ${{ steps.rel.outputs.prerelease }}
STANDALONE_URL: ${{ steps.rel.outputs.standalone_url }}
APPIMAGE_URL: ${{ steps.rel.outputs.appimage_url }}
run: |
# Rewrite the manifest. Preserve the file's comment header.
if [ "$PRERELEASE" = "false" ]; then
sed -i "s#^RELEASE_URL=.*#RELEASE_URL=$STANDALONE_URL#" scripts/release.env
sed -i "s#^APPIMAGE_RELEASE_URL=.*#APPIMAGE_RELEASE_URL=$APPIMAGE_URL#" scripts/release.env
else
sed -i "s#^PRE_RELEASE_URL=.*#PRE_RELEASE_URL=$STANDALONE_URL#" scripts/release.env
sed -i "s#^APPIMAGE_PRE_RELEASE_URL=.*#APPIMAGE_PRE_RELEASE_URL=$APPIMAGE_URL#" scripts/release.env
fi
- name: Commit manifest
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add scripts/release.env
if git diff --cached --quiet; then
echo "No changes to manifest; skipping commit"
else
git commit -m "chore(release): update install manifest [skip ci]"
git push
fi