release #25
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: Build & Publish Docker | |
| on: | |
| repository_dispatch: | |
| types: [release] | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'App version (e.g. 2.1.0 or 2.1.0-rc.1)' | |
| required: true | |
| dry_run: | |
| description: 'Dry run (draft release, no commit, no latest tag)' | |
| type: boolean | |
| default: false | |
| pe_version: | |
| description: 'PE library version (defaults to app version if not set)' | |
| required: false | |
| permissions: | |
| contents: write | |
| packages: write | |
| env: | |
| DOTNET_VERSION: '10.0.x' | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| jobs: | |
| build-and-push: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Determine parameters | |
| id: params | |
| run: | | |
| if [ "${{ github.event_name }}" = "repository_dispatch" ]; then | |
| VERSION="${{ github.event.client_payload.version }}" | |
| DRY_RUN="${{ github.event.client_payload.dry_run }}" | |
| NUGET_SOURCE="${{ github.event.client_payload.nuget_source }}" | |
| else | |
| VERSION="${{ inputs.version }}" | |
| DRY_RUN="${{ inputs.dry_run }}" | |
| NUGET_SOURCE="github" | |
| fi | |
| # Default to github if not specified (backward compatible) | |
| if [ -z "${NUGET_SOURCE}" ]; then | |
| NUGET_SOURCE="github" | |
| fi | |
| if [ "${{ github.event_name }}" = "repository_dispatch" ]; then | |
| PE_VERSION="${{ github.event.client_payload.pe_version }}" | |
| else | |
| PE_VERSION="${{ inputs.pe_version }}" | |
| fi | |
| # Default pe_version to version if not set (initial release: both identical) | |
| if [ -z "${PE_VERSION}" ]; then | |
| PE_VERSION="${VERSION}" | |
| fi | |
| if [[ "$VERSION" == *-* ]]; then | |
| IS_PRERELEASE="true" | |
| else | |
| IS_PRERELEASE="false" | |
| fi | |
| echo "version=${VERSION}" >> $GITHUB_OUTPUT | |
| echo "is_prerelease=${IS_PRERELEASE}" >> $GITHUB_OUTPUT | |
| echo "dry_run=${DRY_RUN}" >> $GITHUB_OUTPUT | |
| echo "nuget_source=${NUGET_SOURCE}" >> $GITHUB_OUTPUT | |
| echo "pe_version=${PE_VERSION}" >> $GITHUB_OUTPUT | |
| IMAGE_TAG=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') | |
| echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT | |
| - uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.PAT_DISPATCH }} | |
| fetch-depth: 0 | |
| # ── Version Guard ──────────────────────────────── | |
| - name: Check if version already exists | |
| if: steps.params.outputs.dry_run != 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.PAT_DISPATCH }} | |
| script: | | |
| const version = '${{ steps.params.outputs.version }}'; | |
| const tag = `v${version}`; | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const errors = []; | |
| try { | |
| await github.rest.git.getRef({ owner, repo, ref: `tags/${tag}` }); | |
| errors.push(`Git tag '${tag}' already exists`); | |
| } catch (e) { | |
| if (e.status !== 404) throw e; | |
| console.log(`✅ Git tag '${tag}' is available`); | |
| } | |
| try { | |
| await github.rest.repos.getReleaseByTag({ owner, repo, tag }); | |
| errors.push(`GitHub Release '${tag}' already exists`); | |
| } catch (e) { | |
| if (e.status !== 404) throw e; | |
| console.log(`✅ GitHub Release '${tag}' is available`); | |
| } | |
| try { | |
| const packageName = repo.toLowerCase(); | |
| const versions = await github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg({ | |
| package_type: 'container', | |
| package_name: packageName, | |
| org: owner | |
| }); | |
| if (versions.data.some(v => v.metadata?.container?.tags?.includes(version))) { | |
| errors.push(`Docker image tag '${version}' already exists on ghcr.io`); | |
| } else { | |
| console.log(`✅ Docker image tag '${version}' is available`); | |
| } | |
| } catch (e) { | |
| if (e.status === 404) { | |
| console.log(`✅ No container package found yet (first publish)`); | |
| } else { | |
| console.warn(`⚠️ Could not check container registry: ${e.message}`); | |
| } | |
| } | |
| if (errors.length > 0) { | |
| core.setFailed(`❌ Version guard failed:\n${errors.map(e => ` - ${e}`).join('\n')}`); | |
| } else { | |
| console.log('\n✅ All version checks passed'); | |
| } | |
| # ── Update Directory.Build.props ───────────────── | |
| - name: Read current version | |
| id: current | |
| run: | | |
| CURRENT=$(grep -oP '(?<=<Version>)[^<]+' Directory.Build.props) | |
| echo "version=${CURRENT}" >> $GITHUB_OUTPUT | |
| echo "📋 Current version in Directory.Build.props: ${CURRENT}" | |
| - name: Update Directory.Build.props | |
| run: | | |
| VERSION="${{ steps.params.outputs.version }}" | |
| CURRENT="${{ steps.current.outputs.version }}" | |
| if [ "${VERSION}" = "${CURRENT}" ]; then | |
| echo "ℹ️ Version already set to ${VERSION}, no change needed" | |
| else | |
| sed -i "s|<Version>${CURRENT}</Version>|<Version>${VERSION}</Version>|" Directory.Build.props | |
| echo "✅ Updated Directory.Build.props: ${CURRENT} → ${VERSION}" | |
| fi | |
| PE_VERSION="${{ steps.params.outputs.pe_version }}" | |
| sed -i "s|<PayrollEngineVersion>[^<]*</PayrollEngineVersion>|<PayrollEngineVersion>${PE_VERSION}</PayrollEngineVersion>|" Directory.Build.props | |
| echo "✅ PayrollEngineVersion → ${PE_VERSION}" | |
| grep '<Version>\|<PayrollEngineVersion>' Directory.Build.props | |
| - name: Commit version bump | |
| if: steps.params.outputs.dry_run != 'true' | |
| run: | | |
| VERSION="${{ steps.params.outputs.version }}" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add Directory.Build.props | |
| if git diff --cached --quiet; then | |
| echo "ℹ️ No changes to commit (version was already correct)" | |
| else | |
| git commit -m "release: v${VERSION}" | |
| git push | |
| echo "✅ Version bump committed and pushed" | |
| fi | |
| # ── Build & Push Docker ────────────────────────── | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Determine Docker tags | |
| id: tags | |
| run: | | |
| IMAGE="${{ env.REGISTRY }}/${{ steps.params.outputs.image_tag }}" | |
| VERSION="${{ steps.params.outputs.version }}" | |
| IS_PRERELEASE="${{ steps.params.outputs.is_prerelease }}" | |
| DRY_RUN="${{ steps.params.outputs.dry_run }}" | |
| TAGS="${IMAGE}:${VERSION}" | |
| if [ "${IS_PRERELEASE}" = "false" ] && [ "${DRY_RUN}" != "true" ]; then | |
| TAGS="${TAGS},${IMAGE}:latest" | |
| fi | |
| echo "tags=${TAGS}" >> $GITHUB_OUTPUT | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| push: true | |
| build-args: | | |
| GITHUB_TOKEN=${{ secrets.PAT_DISPATCH }} | |
| NUGET_SOURCE=${{ steps.params.outputs.nuget_source }} | |
| tags: ${{ steps.tags.outputs.tags }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ steps.params.outputs.version }} | |
| name: v${{ steps.params.outputs.version }} | |
| prerelease: ${{ steps.params.outputs.is_prerelease }} | |
| draft: ${{ steps.params.outputs.dry_run == 'true' }} | |
| generate_release_notes: true | |