|
| 1 | +name: Release |
| 2 | + |
| 3 | +# Triggered automatically when Directory.Build.props changes on main. |
| 4 | +# Skips silently if the version ends with ".dev" or the tag already exists. |
| 5 | +# Can also be triggered manually via workflow_dispatch — version is always |
| 6 | +# read from Directory.Build.props, never entered as an input. |
| 7 | +# |
| 8 | +# Required secret: PAT_DISPATCH |
| 9 | +# Classic PAT with scopes: repo, workflow, write:packages, read:packages |
| 10 | +# Settings → Secrets → Actions → New repository secret |
| 11 | + |
| 12 | +on: |
| 13 | + push: |
| 14 | + branches: [main] |
| 15 | + paths: |
| 16 | + - Directory.Build.props |
| 17 | + workflow_dispatch: {} |
| 18 | + |
| 19 | +permissions: |
| 20 | + contents: write |
| 21 | + packages: write |
| 22 | + |
| 23 | +env: |
| 24 | + DOTNET_VERSION: '10.0.x' |
| 25 | + |
| 26 | +jobs: |
| 27 | + release: |
| 28 | + runs-on: ubuntu-latest |
| 29 | + steps: |
| 30 | + - uses: actions/checkout@v4 |
| 31 | + with: |
| 32 | + token: ${{ secrets.PAT_DISPATCH }} |
| 33 | + fetch-depth: 0 |
| 34 | + |
| 35 | + # ── Read version from Directory.Build.props ────────────────────────── |
| 36 | + - name: Read version |
| 37 | + id: version |
| 38 | + run: | |
| 39 | + VERSION=$(grep -oP '(?<=<Version>)[^<]+' Directory.Build.props) |
| 40 | + echo "version=${VERSION}" >> $GITHUB_OUTPUT |
| 41 | + echo "Version: ${VERSION}" |
| 42 | +
|
| 43 | + if [[ "${VERSION}" == *".dev"* ]]; then |
| 44 | + echo "is_dev=true" >> $GITHUB_OUTPUT |
| 45 | + echo "⏭️ Development version — skipping release" |
| 46 | + else |
| 47 | + echo "is_dev=false" >> $GITHUB_OUTPUT |
| 48 | + if [[ "${VERSION}" == *-* ]]; then |
| 49 | + echo "is_prerelease=true" >> $GITHUB_OUTPUT |
| 50 | + else |
| 51 | + echo "is_prerelease=false" >> $GITHUB_OUTPUT |
| 52 | + fi |
| 53 | + fi |
| 54 | +
|
| 55 | + # ── Version guard — idempotent skip if tag already exists ──────────── |
| 56 | + - name: Version guard |
| 57 | + if: steps.version.outputs.is_dev != 'true' |
| 58 | + id: guard |
| 59 | + uses: actions/github-script@v7 |
| 60 | + with: |
| 61 | + github-token: ${{ secrets.PAT_DISPATCH }} |
| 62 | + script: | |
| 63 | + const version = '${{ steps.version.outputs.version }}'; |
| 64 | + const tag = `v${version}`; |
| 65 | + const owner = context.repo.owner; |
| 66 | + const repo = context.repo.repo; |
| 67 | +
|
| 68 | + // Check Git tag |
| 69 | + try { |
| 70 | + await github.rest.git.getRef({ owner, repo, ref: `tags/${tag}` }); |
| 71 | + console.log(`⏭️ Tag '${tag}' already exists — skipping release`); |
| 72 | + core.setOutput('skip', 'true'); |
| 73 | + return; |
| 74 | + } catch (e) { |
| 75 | + if (e.status !== 404) throw e; |
| 76 | + } |
| 77 | +
|
| 78 | + // Check GitHub Release |
| 79 | + try { |
| 80 | + await github.rest.repos.getReleaseByTag({ owner, repo, tag }); |
| 81 | + console.log(`⏭️ Release '${tag}' already exists — skipping release`); |
| 82 | + core.setOutput('skip', 'true'); |
| 83 | + return; |
| 84 | + } catch (e) { |
| 85 | + if (e.status !== 404) throw e; |
| 86 | + } |
| 87 | +
|
| 88 | + console.log(`✅ Version ${version} is clean — proceeding`); |
| 89 | + core.setOutput('skip', 'false'); |
| 90 | +
|
| 91 | + # ── Sync regulation-package.json version ───────────────────────────── |
| 92 | + - name: Sync regulation-package.json |
| 93 | + if: steps.version.outputs.is_dev != 'true' && steps.guard.outputs.skip != 'true' |
| 94 | + run: | |
| 95 | + VERSION="${{ steps.version.outputs.version }}" |
| 96 | + if [ -f regulation-package.json ]; then |
| 97 | + if command -v jq &>/dev/null; then |
| 98 | + jq --arg v "$VERSION" '.version = $v' regulation-package.json \ |
| 99 | + > regulation-package.tmp.json && mv regulation-package.tmp.json regulation-package.json |
| 100 | + echo "✅ regulation-package.json version set to ${VERSION}" |
| 101 | + else |
| 102 | + sed -i "s|\"version\": \"[^\"]*\"|\"version\": \"${VERSION}\"|" regulation-package.json |
| 103 | + echo "✅ regulation-package.json version set to ${VERSION} (sed)" |
| 104 | + fi |
| 105 | + fi |
| 106 | +
|
| 107 | + # ── Build ───────────────────────────────────────────────────────────── |
| 108 | + - name: Setup .NET |
| 109 | + if: steps.version.outputs.is_dev != 'true' && steps.guard.outputs.skip != 'true' |
| 110 | + uses: actions/setup-dotnet@v4 |
| 111 | + with: |
| 112 | + dotnet-version: ${{ env.DOTNET_VERSION }} |
| 113 | + |
| 114 | + - name: Configure GitHub Packages |
| 115 | + if: steps.version.outputs.is_dev != 'true' && steps.guard.outputs.skip != 'true' |
| 116 | + run: | |
| 117 | + # Remove local dev source (not available in CI) |
| 118 | + dotnet nuget remove source PayrollEngine 2>/dev/null || true |
| 119 | + dotnet nuget add source \ |
| 120 | + "https://nuget.pkg.github.com/Payroll-Engine/index.json" \ |
| 121 | + --name github \ |
| 122 | + --username github-actions \ |
| 123 | + --password ${{ secrets.PAT_DISPATCH }} \ |
| 124 | + --store-password-in-clear-text |
| 125 | +
|
| 126 | + - name: Restore |
| 127 | + if: steps.version.outputs.is_dev != 'true' && steps.guard.outputs.skip != 'true' |
| 128 | + run: dotnet restore |
| 129 | + |
| 130 | + - name: Build |
| 131 | + if: steps.version.outputs.is_dev != 'true' && steps.guard.outputs.skip != 'true' |
| 132 | + run: dotnet build --configuration Release --no-restore |
| 133 | + |
| 134 | + - name: Pack |
| 135 | + if: steps.version.outputs.is_dev != 'true' && steps.guard.outputs.skip != 'true' |
| 136 | + run: dotnet pack --configuration Release --no-build --output ./nupkgs |
| 137 | + |
| 138 | + # ── Publish to GitHub Packages ──────────────────────────────────────── |
| 139 | + # Package visibility inherits from the repository: |
| 140 | + # public repo → public package (accessible without authentication) |
| 141 | + # private repo → private package (requires read:packages PAT) |
| 142 | + - name: Publish to GitHub Packages |
| 143 | + if: steps.version.outputs.is_dev != 'true' && steps.guard.outputs.skip != 'true' |
| 144 | + run: | |
| 145 | + dotnet nuget push ./nupkgs/*.nupkg \ |
| 146 | + --source "https://nuget.pkg.github.com/Payroll-Engine/index.json" \ |
| 147 | + --api-key ${{ secrets.GITHUB_TOKEN }} \ |
| 148 | + --skip-duplicate |
| 149 | +
|
| 150 | + # ── Create GitHub Release with .nupkg as asset ─────────────────────── |
| 151 | + # The .nupkg asset enables direct URL installation via: |
| 152 | + # InstallRegulationPackage <release-asset-url> <tenant> |
| 153 | + - name: Create GitHub Release |
| 154 | + if: steps.version.outputs.is_dev != 'true' && steps.guard.outputs.skip != 'true' |
| 155 | + uses: softprops/action-gh-release@v2 |
| 156 | + with: |
| 157 | + tag_name: v${{ steps.version.outputs.version }} |
| 158 | + name: v${{ steps.version.outputs.version }} |
| 159 | + prerelease: ${{ steps.version.outputs.is_prerelease }} |
| 160 | + generate_release_notes: true |
| 161 | + files: ./nupkgs/*.nupkg |
0 commit comments