name: Release on: schedule: - cron: '0 9 * * *' workflow_dispatch: inputs: web: description: 'Release Web?' required: true type: boolean default: false desktop: description: 'Release Desktop?' required: true type: boolean default: false mobile: description: 'Release Mobile?' required: true type: boolean default: false ios-app-version: description: 'iOS App Store Version (Optional, use tag version if empty)' required: false type: string permissions: contents: write pull-requests: write actions: write id-token: write packages: write security-events: write attestations: write issues: write jobs: prepare: name: Prepare runs-on: ubuntu-latest outputs: APP_VERSION: ${{ steps.prepare.outputs.APP_VERSION }} GIT_SHORT_HASH: ${{ steps.prepare.outputs.GIT_SHORT_HASH }} BUILD_TYPE: ${{ steps.prepare.outputs.BUILD_TYPE }} steps: - uses: actions/checkout@v4 - name: Prepare Release id: prepare uses: ./.github/actions/prepare-release canary-gate: name: Canary Gate runs-on: ubuntu-latest needs: - prepare outputs: SHOULD_RELEASE: ${{ steps.decide.outputs.SHOULD_RELEASE }} LAST_CANARY_TAG: ${{ steps.decide.outputs.LAST_CANARY_TAG }} LAST_CANARY_SHA: ${{ steps.decide.outputs.LAST_CANARY_SHA }} steps: - name: Decide whether to release id: decide uses: actions/github-script@v7 with: script: | const buildType = '${{ needs.prepare.outputs.BUILD_TYPE }}' if (buildType !== 'canary') { core.setOutput('SHOULD_RELEASE', 'true') return } const owner = context.repo.owner const repo = context.repo.repo const currentSha = context.sha const canaryTagRe = /^v\d+\.\d+\.\d+-canary\.[0-9a-f]+$/i let page = 1 const perPage = 100 let lastCanary = null while (!lastCanary && page <= 10) { const { data } = await github.rest.repos.listTags({ owner, repo, per_page: perPage, page, }) for (const tag of data) { if (canaryTagRe.test(tag.name)) { lastCanary = tag break } } if (data.length < perPage) break page++ } if (!lastCanary) { core.warning('No canary tags found; proceeding with canary release.') core.setOutput('SHOULD_RELEASE', 'true') return } core.setOutput('LAST_CANARY_TAG', lastCanary.name) core.setOutput('LAST_CANARY_SHA', lastCanary.commit.sha) const shouldRelease = lastCanary.commit.sha !== currentSha core.info(`Latest canary tag ${lastCanary.name} -> ${lastCanary.commit.sha}; current ${currentSha}; should_release=${shouldRelease}`) core.setOutput('SHOULD_RELEASE', shouldRelease ? 'true' : 'false') cloud: name: Release Cloud if: ${{ inputs.web || github.event_name != 'workflow_dispatch' }} needs: - prepare uses: ./.github/workflows/release-cloud.yml secrets: inherit with: build-type: ${{ needs.prepare.outputs.BUILD_TYPE }} app-version: ${{ needs.prepare.outputs.APP_VERSION }} git-short-hash: ${{ needs.prepare.outputs.GIT_SHORT_HASH }} image: name: Release Docker Image if: ${{ needs.canary-gate.outputs.SHOULD_RELEASE == 'true' }} runs-on: ubuntu-latest needs: - prepare - canary-gate - cloud steps: - uses: trstringer/manual-approval@v1 if: ${{ needs.prepare.outputs.BUILD_TYPE == 'stable' }} name: Wait for approval with: secret: ${{ secrets.GITHUB_TOKEN }} approvers: darkskygit,pengx17,L-Sun,EYHN minimum-approvals: 1 fail-on-denial: true issue-title: Please confirm to release docker image issue-body: | Env: ${{ needs.prepare.outputs.BUILD_TYPE }} Candidate: ghcr.io/toeverything/affine:${{ needs.prepare.outputs.BUILD_TYPE }}-${{ needs.prepare.outputs.GIT_SHORT_HASH }} Tag: ghcr.io/toeverything/affine:${{ needs.prepare.outputs.BUILD_TYPE }} > comment with "approve", "approved", "lgtm", "yes" to approve > comment with "deny", "denied", "no" to deny - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io logout: false username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Tag Image run: | docker buildx imagetools create --tag ghcr.io/toeverything/affine:${{needs.prepare.outputs.BUILD_TYPE}} ghcr.io/toeverything/affine:${{needs.prepare.outputs.BUILD_TYPE}}-${{needs.prepare.outputs.GIT_SHORT_HASH}} docker buildx imagetools create --tag ghcr.io/toeverything/affine:${{needs.prepare.outputs.APP_VERSION}} ghcr.io/toeverything/affine:${{needs.prepare.outputs.BUILD_TYPE}}-${{needs.prepare.outputs.GIT_SHORT_HASH}} desktop: name: Release Desktop if: ${{ inputs.desktop || github.event_name != 'workflow_dispatch' && needs.canary-gate.outputs.SHOULD_RELEASE == 'true' }} needs: - prepare - canary-gate uses: ./.github/workflows/release-desktop.yml secrets: inherit with: build-type: ${{ needs.prepare.outputs.BUILD_TYPE }} app-version: ${{ needs.prepare.outputs.APP_VERSION }} git-short-hash: ${{ needs.prepare.outputs.GIT_SHORT_HASH }} mobile: name: Release Mobile if: ${{ inputs.mobile }} needs: - prepare uses: ./.github/workflows/release-mobile.yml secrets: inherit with: build-type: ${{ needs.prepare.outputs.BUILD_TYPE }} app-version: ${{ needs.prepare.outputs.APP_VERSION }} git-short-hash: ${{ needs.prepare.outputs.GIT_SHORT_HASH }} ios-app-version: ${{ inputs.ios-app-version }}