Scheduled Release #70
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: Scheduled Release | |
| on: | |
| schedule: | |
| - cron: "0 3 * * *" | |
| workflow_dispatch: | |
| inputs: | |
| force: | |
| description: "Force release (ignore time + PR checks)" | |
| required: false | |
| default: "false" | |
| skip_binaries: | |
| description: "Skip building static binaries (manual release only)" | |
| required: false | |
| default: "false" | |
| jobs: | |
| release: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Get last release info | |
| id: last_release | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const releases = await github.rest.repos.listReleases({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| per_page: 1 | |
| }); | |
| if (!releases.data.length) { | |
| core.setOutput("days", "9999"); | |
| core.setOutput("since", new Date(0).toISOString()); | |
| return; | |
| } | |
| const last = new Date(releases.data[0].published_at); | |
| const now = new Date(); | |
| const diffDays = Math.floor((now - last) / (1000 * 60 * 60 * 24)); | |
| core.setOutput("days", String(diffDays)); | |
| core.setOutput("since", last.toISOString()); | |
| - name: Get ship PRs since last release | |
| id: prs | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const since = new Date("${{ steps.last_release.outputs.since }}"); | |
| const pulls = await github.rest.pulls.list({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: "closed", | |
| sort: "updated", | |
| direction: "desc", | |
| per_page: 100 | |
| }); | |
| const shipPRs = pulls.data.filter(pr => | |
| pr.merged_at && | |
| new Date(pr.merged_at) > since && | |
| pr.labels.some(l => l.name === "ship") | |
| ); | |
| const titles = shipPRs.map(pr => pr.title); | |
| core.setOutput("count", String(shipPRs.length)); | |
| core.setOutput("titles", titles.join("\n")); | |
| - name: Decide release | |
| id: decide | |
| run: | | |
| DAYS=${{ steps.last_release.outputs.days }} | |
| PR_COUNT=${{ steps.prs.outputs.count }} | |
| FORCE="${{ github.event.inputs.force }}" | |
| echo "Days since release: $DAYS" | |
| echo "Ship PRs: $PR_COUNT" | |
| echo "Force mode: $FORCE" | |
| if [ "$FORCE" = "true" ]; then | |
| echo "release=true" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| if [ "$DAYS" -ge 14 ] && [ "$PR_COUNT" -gt 0 ]; then | |
| echo "release=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "release=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Install Nim | |
| if: steps.decide.outputs.release == 'true' | |
| uses: iffy/install-nim@v5 | |
| - name: Build versionctl | |
| if: steps.decide.outputs.release == 'true' | |
| run: nim generate_versionctl | |
| - name: Get version | |
| id: version | |
| if: steps.decide.outputs.release == 'true' | |
| run: | | |
| VERSION="v$(./versionctl print 2>/dev/null)" | |
| if [ -z "$VERSION" ]; then | |
| echo "ERROR: versionctl failed" | |
| exit 1 | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Using version: $VERSION" | |
| - name: Generate changelog | |
| if: steps.decide.outputs.release == 'true' | |
| run: | | |
| echo "## What has Changed (${{ steps.version.outputs.version }})" > CHANGELOG.md | |
| echo "" >> CHANGELOG.md | |
| if [ -z "${{ steps.prs.outputs.titles }}" ]; then | |
| echo "- No changes found" >> CHANGELOG.md | |
| else | |
| echo "${{ steps.prs.outputs.titles }}" | while read line; do | |
| echo "- $line" >> CHANGELOG.md | |
| done | |
| fi | |
| - name: Package config files | |
| if: steps.decide.outputs.release == 'true' | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| tar -czf "catnap-${VERSION}-config.tar.gz" config/ | |
| - name: Create Release | |
| if: steps.decide.outputs.release == 'true' | |
| id: create_release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.version.outputs.version }} | |
| name: ${{ steps.version.outputs.version }} | |
| body_path: CHANGELOG.md | |
| files: catnap-${{ steps.version.outputs.version }}-config.tar.gz | |
| build-binaries: | |
| needs: release | |
| if: | | |
| needs.release.result == 'success' && | |
| needs.release.outputs.version != '' && | |
| github.event.inputs.skip_binaries != 'true' | |
| uses: ./.github/workflows/build-static.yml | |
| attach-binaries: | |
| needs: [release, build-binaries] | |
| if: | | |
| needs.release.outputs.version != '' && | |
| needs.build-binaries.result == 'success' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts/ | |
| - name: Rename binaries with target suffix | |
| run: | | |
| VERSION="${{ needs.release.outputs.version }}" | |
| mkdir -p release-files | |
| for dir in artifacts/catnap-linux-*/; do | |
| arch=$(basename "$dir" | sed 's/catnap-linux-//') | |
| cp "$dir/catnap" "release-files/catnap-${VERSION}-${arch}" | |
| done | |
| ls -la release-files/ | |
| - name: Attach binaries to release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.release.outputs.version }} | |
| files: release-files/* |