source-generator + other fixes (#19) #33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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." |