ci: automate release orchestration for desktop and mobile (#4861)

This commit is contained in:
DIYgod 2026-02-19 16:42:39 +08:00 committed by GitHub
parent d386c00ebd
commit 1e13ce6834
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 197 additions and 50 deletions

View File

@ -16,9 +16,9 @@ const RELEASE_PATTERNS = {
const EXIT_CODES = {
SUCCESS: 0,
NO_RELEASE_FOUND: 1,
GIT_ERROR: 2,
ENV_ERROR: 3,
OUTPUT_ERROR: 4,
}
/**
@ -38,6 +38,23 @@ function setGitHubEnv(key, value) {
}
}
/**
* Write output variable to GitHub Output
* @param {string} key - Output key
* @param {string} value - Output value
*/
function setGitHubOutput(key, value) {
try {
if (!process.env.GITHUB_OUTPUT) {
return
}
appendFileSync(process.env.GITHUB_OUTPUT, `${key}=${value}\n`)
} catch (error) {
console.error(`Failed to set output variable ${key}:`, error.message)
process.exit(EXIT_CODES.OUTPUT_ERROR)
}
}
/**
* Get the latest commit message
* @returns {string} Latest commit message
@ -88,7 +105,7 @@ function main() {
if (!releaseInfo) {
console.info("No desktop or mobile release found in commit message.")
process.exit(EXIT_CODES.NO_RELEASE_FOUND)
process.exit(EXIT_CODES.SUCCESS)
}
const { platform, version, tagName } = releaseInfo
@ -97,6 +114,9 @@ function main() {
setGitHubEnv("tag_version", tagName)
setGitHubEnv("platform", platform)
setGitHubEnv("version", version)
setGitHubOutput("tag_version", tagName)
setGitHubOutput("platform", platform)
setGitHubOutput("version", version)
console.info(`Found ${platform} release: ${version}`)
console.info(`Tag will be created: ${tagName}`)

View File

@ -28,7 +28,7 @@ concurrency:
jobs:
build:
name: Build Android apk for device
if: github.secret_source != 'None'
if: github.secret_source != 'None' && (github.event_name != 'push' || !startsWith(github.event.head_commit.message || '', 'release(mobile):'))
runs-on: ubuntu-latest
steps:
@ -104,13 +104,34 @@ jobs:
with:
type: "mobile"
- name: Prepare Release Notes
if: github.event.inputs.release == 'true'
id: release_notes
run: |
version="${{ steps.version.outputs.APP_VERSION }}"
changelog_file="apps/mobile/changelog/${version}.md"
release_notes_file="$RUNNER_TEMP/mobile-release-notes.md"
if [ -f "$changelog_file" ]; then
cp "$changelog_file" "$release_notes_file"
else
{
echo "# What's New in v${version}"
echo
echo "- No changelog file found at ${changelog_file}."
} > "$release_notes_file"
fi
echo "body_path=${release_notes_file}" >> "$GITHUB_OUTPUT"
- name: Create Release Draft
if: github.event.inputs.release == 'true'
uses: softprops/action-gh-release@v2
with:
name: Mobile v${{ steps.version.outputs.APP_VERSION }}
draft: false
draft: true
prerelease: true
tag_name: mobile/v${{ steps.version.outputs.APP_VERSION }}
body_path: ${{ steps.release_notes.outputs.body_path }}
# .aab cannot be installed directly on your Android Emulator or device.
files: ${{ github.workspace }}/build.apk

View File

@ -37,6 +37,7 @@ env:
jobs:
release:
if: github.secret_source != 'None' && (github.event_name != 'push' || !startsWith(github.event.head_commit.message || '', 'release(desktop):'))
runs-on: ${{ matrix.os }}
env:
PROD: ${{ github.event.inputs.tag_version == 'true' || github.ref_type == 'tag' || github.event.inputs.store == 'true' }}
@ -293,6 +294,26 @@ jobs:
with:
type: "desktop"
- name: Prepare Release Notes
if: env.RELEASE == 'true'
id: release_notes
run: |
version="${{ steps.version.outputs.APP_VERSION }}"
changelog_file="apps/desktop/changelog/${version}.md"
release_notes_file="$RUNNER_TEMP/desktop-release-notes.md"
if [ -f "$changelog_file" ]; then
cp "$changelog_file" "$release_notes_file"
else
{
echo "# What's New in v${version}"
echo
echo "- No changelog file found at ${changelog_file}."
} > "$release_notes_file"
fi
echo "body_path=${release_notes_file}" >> "$GITHUB_OUTPUT"
- name: Create Release Draft
if: env.RELEASE == 'true'
uses: softprops/action-gh-release@v2
@ -301,6 +322,7 @@ jobs:
draft: false
prerelease: true
tag_name: desktop/v${{ steps.version.outputs.APP_VERSION }}
body_path: ${{ steps.release_notes.outputs.body_path }}
files: |
apps/desktop/out/make/**/Folo-*.dmg
apps/desktop/out/make/**/Folo-*.zip

View File

@ -23,6 +23,7 @@ concurrency:
jobs:
check-runner:
if: github.secret_source != 'None' && (github.event_name != 'push' || !startsWith(github.event.head_commit.message || '', 'release(mobile):'))
runs-on: ubuntu-latest
outputs:
runner-label: ${{ steps.set-runner.outputs.runner-label }}
@ -41,7 +42,7 @@ jobs:
build-ipa-self-hosted:
name: Build iOS IPA (self-hosted)
if: github.secret_source != 'None' && needs.check-runner.outputs.runner-label == 'self-hosted'
if: needs.check-runner.outputs.runner-label == 'self-hosted'
needs: check-runner
runs-on: [self-hosted, macOS]
@ -84,7 +85,7 @@ jobs:
build-ipa-github:
name: Build iOS IPA (GitHub)
if: github.secret_source != 'None' && needs.check-runner.outputs.runner-label == 'macos-latest'
if: needs.check-runner.outputs.runner-label == 'macos-latest'
needs: check-runner
runs-on: macos-latest

View File

@ -1,13 +1,20 @@
name: 🔄 Branch Synchronization
name: 🔄 Sync Release Branches To Dev
on:
push:
branches:
- main
- mobile-main
permissions:
contents: write
jobs:
sync-branches:
sync-to-dev:
runs-on: ubuntu-latest
if: |
(github.ref == 'refs/heads/main' && startsWith(github.event.head_commit.message, 'release(desktop):')) ||
(github.ref == 'refs/heads/mobile-main' && startsWith(github.event.head_commit.message, 'release(mobile):'))
steps:
- name: Checkout repository
uses: actions/checkout@v6
@ -16,27 +23,13 @@ jobs:
- name: Set up Git
run: |
git config --global user.name 'GitHub Actions'
git config --global user.email 'actions@github.com'
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Prepare sync branch
id: branch
- name: Merge source branch into dev
run: |
echo "SYNC_BRANCH_MAIN_DEV=sync/main-to-dev-$(date +'%Y%m%d')" >> $GITHUB_ENV
- name: Sync main to dev
if: github.ref == 'refs/heads/main'
run: |
# Sync main to dev
git checkout main
SYNC_BRANCH_DEV=${{ env.SYNC_BRANCH_MAIN_DEV }}
git checkout -B $SYNC_BRANCH_DEV
DIFF=$(git diff origin/dev...)
if [ -z "$DIFF" ]; then
echo "No changes to sync"
exit 0
fi
git push origin $SYNC_BRANCH_DEV -f
gh pr create --base dev --head $SYNC_BRANCH_DEV --title "Sync main branch to dev branch" --body "Automatic sync" || exit 0
env:
GH_TOKEN: ${{ github.token }}
source_branch="${GITHUB_REF_NAME}"
git fetch origin main mobile-main dev
git checkout dev
git merge --no-ff "origin/${source_branch}" -m "chore(sync): merge ${source_branch} into dev"
git push origin dev

View File

@ -1,18 +1,24 @@
name: 🏷️ Tag on SemVer Commit
name: 🏷️ Release Orchestrator
on:
push:
branches:
- main
- mobile-main
permissions:
contents: write
actions: write
jobs:
create_tag:
name: Create Release Tag
runs-on: ubuntu-latest
outputs:
tag_version: ${{ env.tag_version }}
platform: ${{ env.platform }}
version: ${{ env.version }}
tag_version: ${{ steps.release_info.outputs.tag_version }}
platform: ${{ steps.release_info.outputs.platform }}
version: ${{ steps.release_info.outputs.version }}
ref_name: ${{ steps.release_info.outputs.ref_name }}
steps:
- name: Checkout repository
@ -30,36 +36,82 @@ jobs:
- name: Extract release information
id: extract_info
run: .github/scripts/extract-release-info.mjs
continue-on-error: false
continue-on-error: true
- name: Expose release outputs
id: release_info
run: |
echo "tag_version=${tag_version:-}" >> "$GITHUB_OUTPUT"
echo "platform=${platform:-}" >> "$GITHUB_OUTPUT"
echo "version=${version:-}" >> "$GITHUB_OUTPUT"
echo "ref_name=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
- name: Validate git configuration
if: env.tag_version
if: steps.release_info.outputs.tag_version != ''
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Create and push tag
if: env.tag_version
if: steps.release_info.outputs.tag_version != ''
run: |
echo "Creating tag: ${{ env.tag_version }}"
if git rev-parse "${{ env.tag_version }}" >/dev/null 2>&1; then
echo "Tag ${{ env.tag_version }} already exists, skipping creation"
git fetch --tags --force
echo "Creating tag: ${{ steps.release_info.outputs.tag_version }}"
if git rev-parse "${{ steps.release_info.outputs.tag_version }}" >/dev/null 2>&1; then
echo "Tag ${{ steps.release_info.outputs.tag_version }} already exists, skipping creation"
exit 0
fi
git tag "${{ env.tag_version }}"
git push origin "${{ env.tag_version }}"
echo "Successfully created and pushed tag: ${{ env.tag_version }}"
git tag "${{ steps.release_info.outputs.tag_version }}"
git push origin "${{ steps.release_info.outputs.tag_version }}"
echo "Successfully created and pushed tag: ${{ steps.release_info.outputs.tag_version }}"
trigger_builds:
name: Trigger Platform Builds
needs: create_tag
if: needs.create_tag.outputs.tag_version
if: needs.create_tag.outputs.tag_version != ''
runs-on: ubuntu-latest
steps:
- name: Checkout repository
if: needs.create_tag.outputs.platform == 'desktop' && needs.create_tag.outputs.ref_name == 'main'
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Configure Git
if: needs.create_tag.outputs.platform == 'desktop' && needs.create_tag.outputs.ref_name == 'main'
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Resolve desktop build version
id: desktop_build_version
if: needs.create_tag.outputs.platform == 'desktop' && needs.create_tag.outputs.ref_name == 'main'
run: |
git fetch --tags --force
latest_tag="$(git tag -l 'desktop-build/v*' --sort=-v:refname | head -n 1)"
if [ -z "$latest_tag" ]; then
last_build=110
else
last_build="${latest_tag#desktop-build/v}"
fi
next_build=$((last_build + 1))
build_tag="desktop-build/v${next_build}"
if git rev-parse "$build_tag" >/dev/null 2>&1; then
echo "Build tag $build_tag already exists."
exit 1
fi
git tag "$build_tag"
git push origin "$build_tag"
echo "build_version=${next_build}" >> "$GITHUB_OUTPUT"
echo "Desktop build version: ${next_build}"
- name: Trigger Desktop Build
if: needs.create_tag.outputs.platform == 'desktop'
if: needs.create_tag.outputs.platform == 'desktop' && needs.create_tag.outputs.ref_name == 'main'
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
@ -70,13 +122,15 @@ jobs:
workflow_id: 'build-desktop.yml',
ref: 'main',
inputs: {
tag_version: 'true'
tag_version: 'true',
store: 'true',
build_version: '${{ steps.desktop_build_version.outputs.build_version }}'
}
});
console.log('Desktop build triggered successfully');
- name: Trigger Mobile Build
if: needs.create_tag.outputs.platform == 'mobile'
- name: Trigger Mobile Preview Release Build
if: needs.create_tag.outputs.platform == 'mobile' && needs.create_tag.outputs.ref_name == 'mobile-main'
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
@ -85,9 +139,45 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'build-android.yml',
ref: 'main',
ref: 'mobile-main',
inputs: {
profile: 'preview',
release: 'true'
}
});
console.log('Mobile build triggered successfully');
console.log('Mobile preview release build triggered successfully');
- name: Trigger Mobile Production Android Build
if: needs.create_tag.outputs.platform == 'mobile' && needs.create_tag.outputs.ref_name == 'mobile-main'
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const response = await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'build-android.yml',
ref: 'mobile-main',
inputs: {
profile: 'production',
release: 'false'
}
});
console.log('Mobile production Android build triggered successfully');
- name: Trigger Mobile Production iOS Build
if: needs.create_tag.outputs.platform == 'mobile' && needs.create_tag.outputs.ref_name == 'mobile-main'
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const response = await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'build-ios.yml',
ref: 'mobile-main',
inputs: {
profile: 'production'
}
});
console.log('Mobile production iOS build triggered successfully');