Release #29
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: | |
| schedule: | |
| # 09:00 Asia/Shanghai = 01:00 UTC | |
| - cron: '0 1 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| force_release: | |
| description: Force create a release even if the image fingerprint is unchanged | |
| required: false | |
| default: false | |
| type: boolean | |
| push: | |
| tags: | |
| - 'v*' | |
| permissions: | |
| contents: write | |
| packages: write | |
| jobs: | |
| prepare-scheduled-release: | |
| name: Prepare Scheduled Release | |
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_release: ${{ steps.decision.outputs.should_release }} | |
| tag_name: ${{ steps.decision.outputs.tag_name }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Build candidate image | |
| shell: bash | |
| run: | | |
| docker build --pull -t clawmanager:release-candidate . | |
| - name: Compute release fingerprint | |
| shell: bash | |
| run: | | |
| IMAGE_FINGERPRINT="$(python3 .github/scripts/compute_image_fingerprint.py clawmanager:release-candidate)" | |
| echo "IMAGE_FINGERPRINT=${IMAGE_FINGERPRINT}" >> "$GITHUB_ENV" | |
| - name: Read latest release fingerprint | |
| id: latest-release | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| try { | |
| const release = await github.rest.repos.getLatestRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| }); | |
| const body = release.data.body || ""; | |
| const match = body.match(/Build-Fingerprint:\s*`?([^\s`]+)`?/); | |
| core.setOutput("tag", release.data.tag_name || ""); | |
| core.setOutput("fingerprint", match ? match[1] : ""); | |
| } catch (error) { | |
| if (error.status === 404) { | |
| core.setOutput("tag", ""); | |
| core.setOutput("fingerprint", ""); | |
| return; | |
| } | |
| throw error; | |
| } | |
| - name: Decide whether to create a new release tag | |
| id: decision | |
| shell: bash | |
| env: | |
| FORCE_RELEASE: ${{ github.event.inputs.force_release || 'false' }} | |
| LAST_FINGERPRINT: ${{ steps.latest-release.outputs.fingerprint }} | |
| run: | | |
| CURRENT_FINGERPRINT="${IMAGE_FINGERPRINT}" | |
| TAG_NAME="v$(date -u +'%Y.%-m.%-d')" | |
| LAST_FINGERPRINT="${LAST_FINGERPRINT//\`/}" | |
| CURRENT_FINGERPRINT="${CURRENT_FINGERPRINT//\`/}" | |
| if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then | |
| echo "should_release=false" >> "$GITHUB_OUTPUT" | |
| echo "tag_name=" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [ "$FORCE_RELEASE" = "true" ]; then | |
| echo "should_release=true" >> "$GITHUB_OUTPUT" | |
| echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [ -n "$LAST_FINGERPRINT" ] && [ "$LAST_FINGERPRINT" = "$CURRENT_FINGERPRINT" ]; then | |
| echo "should_release=false" >> "$GITHUB_OUTPUT" | |
| echo "tag_name=" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "should_release=true" >> "$GITHUB_OUTPUT" | |
| echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT" | |
| - name: Create and push release tag | |
| if: steps.decision.outputs.should_release == 'true' | |
| shell: bash | |
| run: | | |
| TAG_NAME="${{ steps.decision.outputs.tag_name }}" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git tag -a "$TAG_NAME" -m "Release $TAG_NAME" | |
| git push origin "$TAG_NAME" | |
| release: | |
| name: Publish Release | |
| needs: | |
| - prepare-scheduled-release | |
| if: | | |
| always() && ( | |
| github.event_name == 'push' || | |
| needs.prepare-scheduled-release.outputs.should_release == 'true' | |
| ) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set release metadata | |
| shell: bash | |
| run: | | |
| REPOSITORY_OWNER_LOWER="$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')" | |
| if [ "${{ github.event_name }}" = "push" ]; then | |
| echo "TAG_NAME=${GITHUB_REF_NAME}" >> "$GITHUB_ENV" | |
| else | |
| echo "TAG_NAME=${{ needs.prepare-scheduled-release.outputs.tag_name }}" >> "$GITHUB_ENV" | |
| fi | |
| echo "IMAGE_URI=ghcr.io/${REPOSITORY_OWNER_LOWER}/clawmanager" >> "$GITHUB_ENV" | |
| - name: Checkout release tag | |
| if: github.event_name != 'push' | |
| run: git checkout "${TAG_NAME}" | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Build release image | |
| shell: bash | |
| run: | | |
| docker build --pull \ | |
| -t "${IMAGE_URI}:${TAG_NAME}" \ | |
| -t "${IMAGE_URI}:latest" \ | |
| . | |
| - name: Compute release fingerprint | |
| shell: bash | |
| run: | | |
| IMAGE_FINGERPRINT="$(python3 .github/scripts/compute_image_fingerprint.py "${IMAGE_URI}:${TAG_NAME}")" | |
| echo "IMAGE_FINGERPRINT=${IMAGE_FINGERPRINT}" >> "$GITHUB_ENV" | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Push release image | |
| shell: bash | |
| run: | | |
| docker push "${IMAGE_URI}:${TAG_NAME}" | |
| docker push "${IMAGE_URI}:latest" | |
| - name: Read pushed image digest | |
| shell: bash | |
| run: | | |
| IMAGE_DIGEST="$(docker buildx imagetools inspect "${IMAGE_URI}:${TAG_NAME}" | sed -n 's/^Digest:[[:space:]]*//p' | head -n1)" | |
| if [ -z "${IMAGE_DIGEST}" ]; then | |
| echo "Failed to resolve image digest for ${IMAGE_URI}:${TAG_NAME}" >&2 | |
| exit 1 | |
| fi | |
| echo "IMAGE_DIGEST=${IMAGE_DIGEST}" >> "$GITHUB_ENV" | |
| - name: Build release notes | |
| shell: bash | |
| run: | | |
| cat <<EOF > /tmp/release-highlights.md | |
| ## Highlights | |
| - Added automated release flow for scheduled checks and \`v*\` tag publishing. | |
| - Added in-app delete confirmation dialogs instead of browser-native prompts. | |
| - Fixed WebSocket authentication so real-time status updates can connect in browser environments. | |
| - Fixed Linux container startup script handling and enforced LF line endings for shell scripts. | |
| ## Release Assets | |
| Image: \`${IMAGE_URI}:${TAG_NAME}\` | |
| Latest: \`${IMAGE_URI}:latest\` | |
| Image-Digest: \`${IMAGE_DIGEST}\` | |
| Build-Fingerprint: \`${IMAGE_FINGERPRINT}\` | |
| EOF | |
| - name: Create GitHub release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ env.TAG_NAME }} | |
| generate_release_notes: true | |
| body_path: /tmp/release-highlights.md |