diff --git a/.github/scripts/extract-release-info.mjs b/.github/scripts/extract-release-info.mjs index d9d878369..5062cb77f 100755 --- a/.github/scripts/extract-release-info.mjs +++ b/.github/scripts/extract-release-info.mjs @@ -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}`) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index fb66914ff..6633e5cb5 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -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 diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index c8cbadd07..a51ec95f8 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -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 diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index 07b6a57b7..06a86fd8a 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -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 diff --git a/.github/workflows/sync.yaml b/.github/workflows/sync.yaml index ee0f9e36c..dd0847a30 100644 --- a/.github/workflows/sync.yaml +++ b/.github/workflows/sync.yaml @@ -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 diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 13da44f27..def2232d0 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -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');