Skip to content

source-generator + other fixes (#19) #33

source-generator + other fixes (#19)

source-generator + other fixes (#19) #33

Workflow file for this run

name: CD
permissions:
contents: write
concurrency:
group: cd-${{ github.ref }}
cancel-in-progress: true
on:
workflow_dispatch:
push:
branches:
- main
paths:
- .github/workflows/cd.yml
- Justfile
- global.json
- package.json
- src/**
jobs:
release:
name: Build and Release
runs-on: ubuntu-latest
env:
CONFIGURATION: Release
SOLUTION: ./src/AspireC4.slnx
PACKAGE_PROJECT: ./src/src/AspireC4/AspireC4.csproj
ARTIFACTS_DIR: ./artifacts/nuget
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- id: release-trigger
name: Determine whether to publish a release
env:
EVENT_NAME: ${{ github.event_name }}
GITHUB_REF: ${{ github.ref }}
BEFORE_SHA: ${{ github.event.before }}
CURRENT_SHA: ${{ github.sha }}
shell: bash
run: |
set -euo pipefail
if [[ "${GITHUB_REF}" != "refs/heads/main" ]]; then
echo "::error::Releases must only be created from main. Current ref: ${GITHUB_REF}"
exit 1
fi
if [[ "${EVENT_NAME}" == "workflow_dispatch" ]]; then
echo "should_release=true" >> "${GITHUB_OUTPUT}"
exit 0
fi
if [[ "${BEFORE_SHA}" == "0000000000000000000000000000000000000000" ]]; then
changed_files="$(git show --name-only --pretty='' "${CURRENT_SHA}")"
else
changed_files="$(git diff --name-only "${BEFORE_SHA}" "${CURRENT_SHA}")"
fi
if grep -Fxq "package.json" <<< "${changed_files}"; then
echo "should_release=true" >> "${GITHUB_OUTPUT}"
else
echo "should_release=false" >> "${GITHUB_OUTPUT}"
fi
- id: version
name: Read release version
if: ${{ steps.release-trigger.outputs.should_release == 'true' }}
shell: bash
run: |
set -euo pipefail
version="$(node -p "require('./package.json').version")"
echo "value=${version}" >> "${GITHUB_OUTPUT}"
echo "tag=v${version}" >> "${GITHUB_OUTPUT}"
if [[ "${version}" == *-* ]]; then
echo "prerelease=true" >> "${GITHUB_OUTPUT}"
else
echo "prerelease=false" >> "${GITHUB_OUTPUT}"
fi
- name: Block duplicate version
if: ${{ steps.release-trigger.outputs.should_release == 'true' }}
env:
GITHUB_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.version.outputs.tag }}
shell: bash
run: |
set -euo pipefail
if git ls-remote --exit-code --tags origin "refs/tags/${RELEASE_TAG}" > /dev/null 2>&1; then
echo "::error::Tag ${RELEASE_TAG} already exists on origin. Refusing to replace an existing release version."
exit 1
fi
release_status="$(
curl -sS \
-o release.json \
-w "%{http_code}" \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/tags/${RELEASE_TAG}"
)"
if [[ "${release_status}" == "200" ]]; then
echo "::error::Release ${RELEASE_TAG} already exists. Refusing to replace an existing release version."
cat release.json
exit 1
fi
if [[ "${release_status}" != "404" ]]; then
echo "::error::Unexpected GitHub API response while checking ${RELEASE_TAG} (HTTP ${release_status})."
cat release.json
exit 1
fi
rm -f release.json
- name: Set up .NET
uses: actions/setup-dotnet@v5
with:
global-json-file: ./global.json
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version-file: package.json
cache: npm
- name: CSharpier format check
run: |
dotnet tool install --global csharpier --version 1.2.6
csharpier check ./
- name: Restore solution
run: dotnet restore ${{ env.SOLUTION }}
- name: Build solution
run: dotnet build ${{ env.SOLUTION }} --no-restore --configuration ${{ env.CONFIGURATION }}
- name: Run unit tests
run: dotnet test --project src/tests/AspireC4.UnitTests --no-build --verbosity normal --configuration ${{ env.CONFIGURATION }}
- name: Run integration tests
run: dotnet test --project src/tests/AspireC4.IntegrationTests --no-build --verbosity normal --configuration ${{ env.CONFIGURATION }}
- name: Pack NuGet artifacts
if: ${{ steps.release-trigger.outputs.should_release == 'true' }}
env:
PACKAGE_VERSION: ${{ steps.version.outputs.value }}
shell: bash
run: |
set -euo pipefail
mkdir -p "${ARTIFACTS_DIR}"
dotnet pack "${PACKAGE_PROJECT}" \
--configuration "${CONFIGURATION}" \
--no-build \
--output "${ARTIFACTS_DIR}" \
"/p:Version=${PACKAGE_VERSION}" \
"/p:RepositoryBranch=${GITHUB_REF_NAME}" \
"/p:RepositoryCommit=${GITHUB_SHA}" \
"/p:COPYRIGHT_YEAR=$(date -u +%Y)"
- id: changelog
name: Extract changelog section
if: ${{ steps.release-trigger.outputs.should_release == 'true' }}
env:
RELEASE_TAG: ${{ steps.version.outputs.tag }}
shell: bash
run: |
set -euo pipefail
if [[ ! -f CHANGELOG.md ]]; then
echo "body=" >> "${GITHUB_OUTPUT}"
exit 0
fi
# Extract everything between the first H2 heading and the next H2 heading
body="$(awk '/^## /{found++} found==1{print} found==2{exit}' CHANGELOG.md | tail -n +2)"
{
echo "body<<EOF_CHANGELOG"
echo "${body}"
echo "EOF_CHANGELOG"
} >> "${GITHUB_OUTPUT}"
- name: Create GitHub release
if: ${{ steps.release-trigger.outputs.should_release == 'true' }}
env:
GITHUB_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.version.outputs.tag }}
RELEASE_PRERELEASE: ${{ steps.version.outputs.prerelease }}
RELEASE_BODY: ${{ steps.changelog.outputs.body }}
RELEASE_MAKE_LATEST: ${{ steps.version.outputs.prerelease == 'false' && 'true' || 'false' }}
shell: bash
run: |
set -euo pipefail
prerelease_flag=""
if [[ "${RELEASE_PRERELEASE}" == "true" ]]; then
prerelease_flag="--prerelease"
fi
notes_flag=""
if [[ -z "${RELEASE_BODY}" ]]; then
notes_flag="--generate-notes"
fi
# Create as a draft so assets can be uploaded before the release is published.
# Register cleanup so a transient failure at publish does not leave an orphaned
# draft+tag that would block duplicate-version checks on retry.
cleanup() {
echo "::warning::Release step failed — cleaning up draft release and tag to allow a clean retry."
gh release delete "${RELEASE_TAG}" --yes --cleanup-tag 2>/dev/null || true
}
trap cleanup ERR
gh release create "${RELEASE_TAG}" \
--draft \
--title "${RELEASE_TAG}" \
--target "${GITHUB_SHA}" \
${prerelease_flag} \
${notes_flag} \
--notes "${RELEASE_BODY}" \
./artifacts/nuget/*.nupkg \
./artifacts/nuget/*.snupkg
# Publish the draft — assets are already attached, so immutable-release upload errors cannot occur.
# --latest is deferred to here: GitHub rejects that flag on draft releases.
latest_flag=""
if [[ "${RELEASE_MAKE_LATEST}" == "true" ]]; then
latest_flag="--latest"
fi
gh release edit "${RELEASE_TAG}" --draft=false ${latest_flag}
trap - ERR
- name: Skip release publish
if: ${{ steps.release-trigger.outputs.should_release != 'true' }}
run: echo "package.json did not change on this push, so release publishing was skipped."