Release 1.5.0 #116
Workflow file for this run
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: Release | |
| on: | |
| push: | |
| tags: | |
| - "*.*.*" | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Release tag (example: 1.2.3 or 1.2.3-rc.1)" | |
| required: true | |
| type: string | |
| permissions: | |
| contents: write | |
| jobs: | |
| release: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.resolve_version.outputs.version }} | |
| is_prerelease: ${{ steps.resolve_version.outputs.is_prerelease }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version-file: go.mod | |
| cache: true | |
| cache-dependency-path: go.sum | |
| - name: Resolve release version | |
| id: resolve_version | |
| run: | | |
| if [ "${{ github.event_name }}" = "push" ]; then | |
| VERSION="${GITHUB_REF#refs/tags/}" | |
| else | |
| VERSION="${{ github.event.inputs.tag }}" | |
| fi | |
| if [ -z "${VERSION}" ]; then | |
| echo "Error: VERSION is empty" | |
| exit 1 | |
| fi | |
| if [[ "${VERSION}" == v* ]]; then | |
| echo "Error: tags must not use 'v' prefix (got: ${VERSION})" | |
| exit 1 | |
| fi | |
| if ! [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then | |
| echo "Error: VERSION must be SemVer-like (got: ${VERSION})" | |
| exit 1 | |
| fi | |
| BASE_VERSION="${VERSION%%+*}" | |
| if [[ "${BASE_VERSION}" == *-* ]]; then | |
| IS_PRERELEASE=true | |
| else | |
| IS_PRERELEASE=false | |
| fi | |
| echo "VERSION=${VERSION}" >> "${GITHUB_ENV}" | |
| echo "VERSION_SEMVER=${VERSION}" >> "${GITHUB_ENV}" | |
| echo "IS_PRERELEASE=${IS_PRERELEASE}" >> "${GITHUB_ENV}" | |
| echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" | |
| echo "is_prerelease=${IS_PRERELEASE}" >> "${GITHUB_OUTPUT}" | |
| - name: Download dependencies | |
| run: go mod download | |
| - name: Verify dependencies | |
| run: go mod verify | |
| - name: Sync VERSION file with tag | |
| run: echo "${{ env.VERSION_SEMVER }}" > VERSION | |
| - name: Run tests | |
| run: make test-ci | |
| - name: Run linter | |
| uses: golangci/golangci-lint-action@v9 | |
| with: | |
| version: v2.10.1 | |
| args: --timeout=5m | |
| - name: Build release artifacts | |
| env: | |
| VERSION: ${{ env.VERSION }} | |
| run: | | |
| make VERSION="${VERSION}" release | |
| ls -la dist/ | |
| - name: Optional release signing and notarization | |
| env: | |
| RELEASE_SIGN: ${{ vars.RELEASE_SIGN }} | |
| RELEASE_NOTARIZE: ${{ vars.RELEASE_NOTARIZE }} | |
| SIGN_IDENTITY: ${{ secrets.APPLE_SIGN_IDENTITY }} | |
| run: | | |
| make release-sign | |
| make notarize-release | |
| - name: Create macOS Universal Binary | |
| run: | | |
| sudo apt-get update && sudo apt-get install -y llvm | |
| if ! command -v llvm-lipo &> /dev/null; then | |
| LIPO_PATH=$(find /usr/bin -name "llvm-lipo-*" | head -n 1) | |
| if [ -n "$LIPO_PATH" ]; then | |
| sudo ln -s "$LIPO_PATH" /usr/bin/llvm-lipo | |
| else | |
| echo "Error: llvm-lipo not found" | |
| exit 1 | |
| fi | |
| fi | |
| llvm-lipo -create -output dist/construct dist/construct-darwin-amd64 dist/construct-darwin-arm64 | |
| file dist/construct | |
| tar -czf dist/construct-cli-macos-universal.tar.gz -C dist construct | |
| rm dist/construct | |
| - name: Prepare Linux Artifacts | |
| run: | | |
| mkdir -p dist/linux-amd64 dist/linux-arm64 | |
| cp dist/construct-linux-amd64 dist/linux-amd64/construct | |
| cp dist/construct-linux-arm64 dist/linux-arm64/construct | |
| tar -czf dist/construct-cli-linux-amd64.tar.gz -C dist/linux-amd64 construct | |
| tar -czf dist/construct-cli-linux-arm64.tar.gz -C dist/linux-arm64 construct | |
| rm -rf dist/linux-amd64 dist/linux-arm64 | |
| - name: Generate checksums | |
| env: | |
| VERSION: ${{ env.VERSION }} | |
| run: | | |
| cd dist | |
| sha256sum \ | |
| "construct-darwin-arm64-${VERSION}.tar.gz" \ | |
| "construct-darwin-amd64-${VERSION}.tar.gz" \ | |
| "construct-linux-amd64-${VERSION}.tar.gz" \ | |
| "construct-linux-arm64-${VERSION}.tar.gz" \ | |
| "construct-cli-macos-universal.tar.gz" \ | |
| "construct-cli-linux-amd64.tar.gz" \ | |
| "construct-cli-linux-arm64.tar.gz" \ | |
| > checksums.txt | |
| - name: Verify release outputs | |
| env: | |
| VERSION: ${{ env.VERSION }} | |
| run: | | |
| set -e | |
| expected_files=( | |
| "dist/construct-darwin-arm64-${VERSION}.tar.gz" | |
| "dist/construct-darwin-amd64-${VERSION}.tar.gz" | |
| "dist/construct-linux-amd64-${VERSION}.tar.gz" | |
| "dist/construct-linux-arm64-${VERSION}.tar.gz" | |
| "dist/construct-cli-macos-universal.tar.gz" | |
| "dist/construct-cli-linux-amd64.tar.gz" | |
| "dist/construct-cli-linux-arm64.tar.gz" | |
| "dist/checksums.txt" | |
| ) | |
| for file in "${expected_files[@]}"; do | |
| if [ -f "${file}" ]; then | |
| size=$(stat -c%s "${file}") | |
| echo "OK ${file} (size: ${size} bytes)" | |
| else | |
| echo "Missing expected artifact: ${file}" | |
| exit 1 | |
| fi | |
| done | |
| - name: Extract release notes from CHANGELOG.md | |
| env: | |
| VERSION_SEMVER: ${{ env.VERSION_SEMVER }} | |
| run: | | |
| set -euo pipefail | |
| changelog_file="CHANGELOG.md" | |
| notes_file="release_notes.md" | |
| start_marker="<!-- RELEASE:START ${VERSION_SEMVER} -->" | |
| end_marker="<!-- RELEASE:END ${VERSION_SEMVER} -->" | |
| if [ -f "${changelog_file}" ]; then | |
| awk -v start="${start_marker}" -v end="${end_marker}" ' | |
| $0 == start { in_section = 1; next } | |
| $0 == end { in_section = 0; exit } | |
| in_section { print } | |
| ' "${changelog_file}" > "${notes_file}" | |
| else | |
| : > "${notes_file}" | |
| fi | |
| if ! grep -q '[^[:space:]]' "${notes_file}"; then | |
| prev_tag=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || true) | |
| if [ -n "${prev_tag}" ]; then | |
| echo "**Full changelog:** ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/compare/${prev_tag}...${VERSION_SEMVER}" > "${notes_file}" | |
| else | |
| echo "**Full changelog:** ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${VERSION_SEMVER}" > "${notes_file}" | |
| fi | |
| fi | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: ${{ env.VERSION }} | |
| name: The Construct CLI ${{ env.VERSION }} | |
| body_path: release_notes.md | |
| draft: false | |
| prerelease: ${{ steps.resolve_version.outputs.is_prerelease == 'true' }} | |
| files: | | |
| dist/construct-darwin-arm64-${{ env.VERSION }}.tar.gz | |
| dist/construct-darwin-amd64-${{ env.VERSION }}.tar.gz | |
| dist/construct-linux-amd64-${{ env.VERSION }}.tar.gz | |
| dist/construct-linux-arm64-${{ env.VERSION }}.tar.gz | |
| dist/construct-cli-macos-universal.tar.gz | |
| dist/construct-cli-linux-amd64.tar.gz | |
| dist/construct-cli-linux-arm64.tar.gz | |
| dist/checksums.txt | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Update release marker file | |
| env: | |
| VERSION: ${{ env.VERSION }} | |
| IS_PRERELEASE: ${{ env.IS_PRERELEASE }} | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| if [ "$IS_PRERELEASE" = "true" ]; then | |
| echo "$VERSION" > VERSION-BETA | |
| git add VERSION-BETA | |
| git commit -m "chore: update VERSION-BETA to $VERSION" || echo "No changes to commit" | |
| else | |
| echo "$VERSION" > VERSION | |
| git add VERSION | |
| git commit -m "chore: update VERSION to $VERSION" || echo "No changes to commit" | |
| fi | |
| git push origin HEAD:main || echo "Failed to push release marker update" | |
| update-tap: | |
| needs: release | |
| if: ${{ needs.release.outputs.is_prerelease != 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Trigger Homebrew Tap Update | |
| uses: actions/github-script@v8 | |
| with: | |
| github-token: ${{ secrets.TAP_GITHUB_TOKEN }} | |
| script: | | |
| await github.rest.actions.createWorkflowDispatch({ | |
| owner: 'EstebanForge', | |
| repo: 'homebrew-tap', | |
| workflow_id: 'update-formula.yml', | |
| ref: 'main', | |
| inputs: { | |
| formula: 'construct-cli', | |
| tag: '${{ needs.release.outputs.version }}', | |
| repository: '${{ github.repository }}' | |
| } | |
| }) |