App Store Approval → Publish Website Changelog #1643
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: App Store Approval → Publish Website Changelog | |
| on: | |
| schedule: | |
| - cron: "0 * * * *" # every 1 hour | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: "Run without merging PR" | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| publish: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| # ------------------------------------------------------------ | |
| # 1. Check App Store approval status | |
| # ------------------------------------------------------------ | |
| - name: Check App Store approval status | |
| id: appstore | |
| run: | | |
| npm init -y >/dev/null | |
| npm install jsonwebtoken node-fetch@2 >/dev/null | |
| node <<'EOF' | |
| const fs = require("fs"); | |
| const jwt = require("jsonwebtoken"); | |
| const fetch = require("node-fetch"); | |
| const { | |
| ASC_ISSUER_ID, | |
| ASC_KEY_ID, | |
| ASC_PRIVATE_KEY, | |
| APP_ID | |
| } = process.env; | |
| const token = jwt.sign({}, ASC_PRIVATE_KEY.replace(/\\n/g, "\n"), { | |
| algorithm: "ES256", | |
| issuer: ASC_ISSUER_ID, | |
| header: { kid: ASC_KEY_ID }, | |
| expiresIn: "10m", | |
| audience: "appstoreconnect-v1", | |
| }); | |
| async function run() { | |
| const res = await fetch( | |
| `https://api.appstoreconnect.apple.com/v1/apps/${APP_ID}/appStoreVersions`, | |
| { headers: { Authorization: `Bearer ${token}` } } | |
| ); | |
| const json = await res.json(); | |
| const version = json.data?.[0]; | |
| const state = version?.attributes?.appStoreState; | |
| console.log("App Store state:", state); | |
| if (state === "READY_FOR_SALE") { | |
| fs.appendFileSync(process.env.GITHUB_OUTPUT, "approved=true\n"); | |
| } else { | |
| fs.appendFileSync(process.env.GITHUB_OUTPUT, "approved=false\n"); | |
| } | |
| } | |
| run(); | |
| EOF | |
| env: | |
| ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} | |
| ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} | |
| ASC_PRIVATE_KEY: ${{ secrets.ASC_PRIVATE_KEY }} | |
| APP_ID: ${{ secrets.APP_ID }} | |
| # ------------------------------------------------------------ | |
| # 2. Find the PR waiting for App Store approval (by label) | |
| # ------------------------------------------------------------ | |
| - name: Find changelog PR by label | |
| if: inputs.dry_run == true || steps.appstore.outputs.approved == 'true' | |
| id: pr | |
| run: | | |
| PRS=$(gh pr list \ | |
| --label "publish-after-appstore-approval" \ | |
| --state open \ | |
| --json number,title \ | |
| --repo "$GITHUB_REPOSITORY") | |
| COUNT=$(echo "$PRS" | jq length) | |
| if [ "$COUNT" -eq 0 ]; then | |
| echo "No PR waiting for approval. Exiting." | |
| exit 0 | |
| fi | |
| if [ "$COUNT" -gt 1 ]; then | |
| echo "❌ Multiple PRs waiting for approval. Abort." | |
| echo "$PRS" | |
| exit 1 | |
| fi | |
| PR_NUMBER=$(echo "$PRS" | jq -r '.[0].number') | |
| echo "Found PR #$PR_NUMBER" | |
| printf "number=%s\n" "$PR_NUMBER" >> "$GITHUB_OUTPUT" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # ------------------------------------------------------------ | |
| # 3. Merge the PR | |
| # ------------------------------------------------------------ | |
| - name: Merge changelog PR | |
| if: steps.pr.outputs.number != '' && steps.appstore.outputs.approved == 'true' && inputs.dry_run != true | |
| run: gh pr merge ${{ steps.pr.outputs.number }} --merge --repo "$GITHUB_REPOSITORY" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # ------------------------------------------------------------ | |
| # 4. Send email notification (with PR link) | |
| # ------------------------------------------------------------ | |
| - name: Send email notification | |
| if: steps.pr.outputs.number != '' && (inputs.dry_run == true || steps.appstore.outputs.approved == 'true') | |
| run: | | |
| npm install nodemailer >/dev/null | |
| node <<'EOF' | |
| const nodemailer = require("nodemailer"); | |
| const prNumber = process.env.PR_NUMBER; | |
| const repo = process.env.GITHUB_REPOSITORY; | |
| const prUrl = `${process.env.GITHUB_SERVER_URL}/${repo}/pull/${prNumber}`; | |
| const transporter = nodemailer.createTransport({ | |
| service: "gmail", | |
| auth: { | |
| user: process.env.EMAIL_SMTP_USER, | |
| pass: process.env.EMAIL_SMTP_PASS, | |
| }, | |
| }); | |
| transporter.sendMail({ | |
| from: `"Joodle Release Bot" <${process.env.EMAIL_SMTP_USER}>`, | |
| to: process.env.EMAIL_TO, | |
| subject: "✅ Joodle changelog published", | |
| text: ` | |
| Apple approved your app 🎉 | |
| The website changelog PR has been merged into main. | |
| PR #${prNumber} | |
| ${prUrl} | |
| Users opening Joodle will now see the latest changelog. | |
| `, | |
| }); | |
| EOF | |
| env: | |
| EMAIL_SMTP_USER: ${{ secrets.EMAIL_SMTP_USER }} | |
| EMAIL_SMTP_PASS: ${{ secrets.EMAIL_SMTP_PASS }} | |
| EMAIL_TO: ${{ secrets.EMAIL_TO }} | |
| PR_NUMBER: ${{ steps.pr.outputs.number }} |