diff --git a/.github/workflows/develop-synced-dispatch.yml b/.github/workflows/develop-synced-dispatch.yml index 3a568b182..d22ff96e5 100644 --- a/.github/workflows/develop-synced-dispatch.yml +++ b/.github/workflows/develop-synced-dispatch.yml @@ -1,59 +1,50 @@ -name: Dispatch develop-synced after release +name: Dispatch develop-synced after sync/main merged (Scheme A) on: - push: + pull_request: + types: + - closed branches: - develop jobs: dispatch_develop_synced: - if: contains(github.event.head_commit.message, 'prelease version') + if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'sync/main-') + runs-on: ubuntu-latest + permissions: contents: write steps: - - name: Checkout develop - uses: actions/checkout@v4 - with: - ref: develop - - - name: Extract version from commit message - id: meta - env: - COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + - name: Derive version from sync branch + id: version run: | set -euo pipefail - echo "Commit message: ${COMMIT_MESSAGE}" - VERSION_WITH_V="$(echo "${COMMIT_MESSAGE}" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' || true)" - if [ -n "${VERSION_WITH_V}" ]; then - VERSION="${VERSION_WITH_V#v}" - else - VERSION="$(echo "${COMMIT_MESSAGE}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1 || true)" - fi - if [ -z "${VERSION}" ]; then - echo "Failed to parse version from commit message" >&2 - exit 1 + BRANCH="${{ github.event.pull_request.head.ref }}" + echo "Head branch: ${BRANCH}" + VERSION="${BRANCH#sync/main-}" + if [ -z "${VERSION}" ] || [ "${VERSION}" = "${BRANCH}" ]; then + echo "Failed to parse version from branch ${BRANCH}, skip dispatch." + echo "version=" >> "$GITHUB_OUTPUT" + exit 0 fi echo "Parsed version: ${VERSION}" echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - - name: Send develop-synced repository_dispatch - uses: actions/github-script@v7 + - name: Send repository_dispatch develop-synced + if: steps.version.outputs.version != '' env: - VERSION: ${{ steps.meta.outputs.version }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const version = process.env.VERSION; - if (!version) { - core.setFailed('VERSION env is not set'); - return; - } - core.info(`Sending repository_dispatch develop-synced for version ${version}`); - await github.rest.repos.createDispatchEvent({ - owner: context.repo.owner, - repo: context.repo.repo, - event_type: 'develop-synced', - client_payload: { version } - }); + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + VERSION="${{ steps.version.outputs.version }}" + OWNER_REPO="${GITHUB_REPOSITORY}" + + echo "Sending repository_dispatch develop-synced for version ${VERSION} to ${OWNER_REPO}" + + curl -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + "https://api.github.com/repos/${OWNER_REPO}/dispatches" \ + -d "{\"event_type\":\"develop-synced\",\"client_payload\":{\"version\":\"${VERSION}\"}}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9599fbc66..94dc6a93d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,107 +3,455 @@ name: Release CI on: push: branches: - - 'release/[0-9]+.[0-9]+.[0-9]+' + - 'release/[0-9]+\.[0-9]+\.[0-9]+' + - 'hotfix/[0-9]+\.[0-9]+\.[0-9]+' + - 'pre-release/[0-9]+\.[0-9]+\.[0-9]+-alpha\.[0-9]+' + - 'pre-release/[0-9]+\.[0-9]+\.[0-9]+-beta\.[0-9]+' + - 'pre-release/[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+' + - 'pre-release/[0-9]+\.[0-9]+\.[0-9]+-hotfix\.[0-9]+' jobs: - build: - runs-on: macOS-latest + release: + runs-on: macos-latest permissions: - contents: write - pull-requests: write + id-token: write # OIDC for npm publish (single entry) + contents: write # push commits / tags + pull-requests: write # create PR from release/* to main strategy: matrix: - node-version: [18.x] + node-version: [20.x] + + concurrency: + group: vtable-release-${{ github.ref_name }} + cancel-in-progress: false steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: Configure git user + - name: Configure Git identity run: | git config user.name ${{ github.actor }} git config user.email ${{ github.actor }}@users.noreply.github.com - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + - name: Setup Node.js + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} + registry-url: 'https://registry.npmjs.org' cache: 'npm' cache-dependency-path: './common/config/rush/pnpm-lock.yaml' + - name: Update npm + run: npm install -g npm@latest + + - name: Install latest pnpm + run: npm install -g pnpm@10.7.0 + + - name: Install Python distutils (macOS) + if: runner.os == 'macOS' + run: | + python3 -m pip install setuptools --break-system-packages + + - name: Install Python distutils (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y python3-distutils + python3 -m pip install setuptools --break-system-packages + + - name: Install native deps for node-canvas (macOS) + if: runner.os == 'macOS' + run: | + brew update + brew install pkg-config cairo pango libpng jpeg giflib librsvg + + - name: Install native deps for node-canvas (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev pkg-config + - name: Install rush run: node common/scripts/install-run-rush.js install --bypass-policy - - name: Parse semver version from branch name - id: semver_parser + # ===== Release flow (stable) ===== + - name: Parse semver (release) + if: startsWith(github.ref_name, 'release/') + id: semver_release uses: xile611/read-package-version-action@main with: path: packages/vtable semver_string: ${{ github.ref_name }} semver_pattern: '^release/(.*)$' - - name: Update nextBump of version policies + - name: update nextBump (release) + if: startsWith(github.ref_name, 'release/') uses: xile611/set-next-bump-of-rush@main with: - release_version: ${{ steps.semver_parser.outputs.full }} + release_version: ${{ steps.semver_release.outputs.full }} write_next_bump: true - - name: Update version + - name: Generate changelog blocks from changefiles (release) + if: startsWith(github.ref_name, 'release/') + env: + CHANGELOG_API_URL: ${{ secrets.VTABLE_CHANGELOG_API_URL }} + CHANGELOG_API_TOKEN: ${{ secrets.VTABLE_CHANGELOG_API_TOKEN }} + RELEASE_VERSION: ${{ steps.semver_release.outputs.main }} + run: | + node <<'NODE' + const fs = require('fs'); + const path = require('path'); + const { execSync } = require('child_process'); + + const baseDir = path.join(process.cwd(), 'common', 'changes', '@visactor', 'vtable'); + let changefiles = []; + + try { + const files = fs.readdirSync(baseDir).filter((name) => name.endsWith('.json')); + for (const file of files) { + const fullPath = path.join(baseDir, file); + const json = JSON.parse(fs.readFileSync(fullPath, 'utf8')); + changefiles.push(json); + } + console.log('Collected changefiles:', changefiles.length); + } catch (e) { + console.log('No changefiles found or unable to read, fallback to empty list.', e.message || e); + } + + const version = process.env.RELEASE_VERSION; + if (!version) { + console.error('Missing RELEASE_VERSION env.'); + process.exit(1); + } + + let prevVersion = process.env.PREV_VERSION || ''; + try { + if (!prevVersion) { + const out = execSync('git tag --list "v*.*.*" --sort=-v:refname', { encoding: 'utf8' }); + const tags = out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean); + if (tags.length > 0) { + const first = tags[0].replace(/^v/i, ''); + if (first !== version && !first.startsWith(version + '-')) { + prevVersion = first; + } else if (tags.length > 1) { + prevVersion = tags[1].replace(/^v/i, ''); + } + } + } + } catch (e) { + console.log('Failed to detect previous version from tags, continue without it:', e.message || e); + } + + const payload = { + version, + prevVersion: prevVersion || undefined, + date: new Date().toISOString().slice(0, 10), + changefiles, + langs: ['en', 'zh'], + reuseHarmonyFromEn: true, + template: 'default', + }; + + const baseUrl = process.env.CHANGELOG_API_URL; + const token = process.env.CHANGELOG_API_TOKEN; + + function writeFallback() { + const date = payload.date; + const enLines = [ + `# v${version}`, + '', + date, + '', + '**🆕 New Features**', + '', + `- TODO: Fill in change details for v${version}.`, + '', + ]; + const zhLines = [ + `# v${version}`, + '', + date, + '', + '**🆕 新增功能**', + '', + `- TODO:补充 v${version} 的更新内容。`, + '', + ]; + fs.mkdirSync('.changelog', { recursive: true }); + fs.writeFileSync('.changelog/en.md', enLines.join('\n') + '\n', 'utf8'); + fs.writeFileSync('.changelog/zh.md', zhLines.join('\n') + '\n', 'utf8'); + fs.writeFileSync('.changelog/harmony.md', enLines.join('\n') + '\n', 'utf8'); + console.log('Wrote fallback changelog blocks for version', version); + } + + if (!baseUrl || !token) { + console.log('CHANGELOG_API_URL or CHANGELOG_API_TOKEN not configured, using fallback template.'); + writeFallback(); + process.exit(0); + } + + const url = new URL('/api/changelog/vtable/generate', baseUrl); + const body = JSON.stringify(payload); + + const isHttps = url.protocol === 'https:'; + const httpModule = isHttps ? require('https') : require('http'); + + const options = { + method: 'POST', + hostname: url.hostname, + port: url.port || (isHttps ? 443 : 80), + path: url.pathname + url.search, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + 'Content-Length': Buffer.byteLength(body), + }, + }; + + const req = httpModule.request(options, (res) => { + let data = ''; + res.on('data', (chunk) => (data += chunk)); + res.on('end', () => { + if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { + try { + const parsed = JSON.parse(data || '{}'); + const en = parsed.blocks && parsed.blocks.en; + const zh = parsed.blocks && parsed.blocks.zh; + const harmony = parsed.harmony || en; + if (!en || !zh) { + throw new Error('Missing en/zh blocks in response'); + } + fs.mkdirSync('.changelog', { recursive: true }); + fs.writeFileSync('.changelog/en.md', String(en).trimEnd() + '\n', 'utf8'); + fs.writeFileSync('.changelog/zh.md', String(zh).trimEnd() + '\n', 'utf8'); + fs.writeFileSync('.changelog/harmony.md', String(harmony).trimEnd() + '\n', 'utf8'); + console.log('Changelog blocks generated via API. traceId:', parsed.traceId || '(none)'); + } catch (e) { + console.error('Failed to parse API response, fallback to template:', e.message || e); + writeFallback(); + } + } else { + console.error('Changelog API returned non-2xx status:', res.statusCode, data); + writeFallback(); + } + }); + }); + + req.on('error', (err) => { + console.error('Changelog API request failed, fallback to template:', err.message || err); + writeFallback(); + }); + + req.write(body); + req.end(); + NODE + + - name: Prepend changelog blocks into release docs (release) + if: startsWith(github.ref_name, 'release/') + run: | + set -euo pipefail + mkdir -p docs/assets/changelog/en docs/assets/changelog/zh + + for lang in en zh; do + block_file=".changelog/${lang}.md" + target_file="docs/assets/changelog/${lang}/release.md" + if [ -f "$block_file" ]; then + if [ -f "$target_file" ]; then + cp "$target_file" "${target_file}.bak" + cat "$block_file" "${target_file}.bak" > "$target_file" + else + cp "$block_file" "$target_file" + fi + else + echo "Missing changelog block for $lang, skip." + fi + done + + harmony_block=".changelog/harmony.md" + harmony_target="packages/harmony_vtable/library/CHANGELOG.md" + if [ -f "$harmony_block" ]; then + if [ -f "$harmony_target" ]; then + cp "$harmony_target" "${harmony_target}.bak" + cat "$harmony_block" "${harmony_target}.bak" > "$harmony_target" + else + cp "$harmony_block" "$harmony_target" + fi + else + echo "Missing harmony changelog block, skip." + fi + + - name: Generate rush version (release) + if: startsWith(github.ref_name, 'release/') run: node common/scripts/install-run-rush.js version --bump - - name: Build packages + - name: Update version (release) + if: startsWith(github.ref_name, 'release/') + run: node common/scripts/apply-release-version.js 'none' ${{ steps.semver_release.outputs.main }} + + - name: Build vutils-extension && vtable env: NODE_OPTIONS: '--max_old_space_size=4096' - NO_EMIT_ON_ERROR: 'true' - run: | - export NODE_OPTIONS="--max_old_space_size=4096" - export NO_EMIT_ON_ERROR="true" - node common/scripts/install-run-rush.js build --only tag:package 2>&1 | tee build.log || { - echo "=== Build failed, showing last 1000 lines of build.log ===" - tail -n 1000 build.log - echo "=== Full error details ===" - grep -A 50 "TypeScript Compilation Errors" build.log || true - grep -A 50 "Build Error" build.log || true - exit 1 - } + run: node common/scripts/install-run-rush.js build --to @visactor/vtable + + - name: Build vtable-extension + if: startsWith(github.ref_name, 'release/') || startsWith(github.ref_name, 'pre-release/') + env: + NODE_OPTIONS: '--max_old_space_size=4096' + run: node common/scripts/install-run-rush.js build --only @visactor/vtable-extension + + - name: Build react-vtable + env: + NODE_OPTIONS: '--max_old_space_size=4096' + run: node common/scripts/install-run-rush.js build --only @visactor/react-vtable + + - name: Build openinula-vtable + env: + NODE_OPTIONS: '--max_old_space_size=4096' + run: node common/scripts/install-run-rush.js build --only @visactor/openinula-vtable + + - name: Build taro-vtable + env: + NODE_OPTIONS: '--max_old_space_size=4096' + run: node common/scripts/install-run-rush.js build --only @visactor/taro-vtable + + - name: Build lark-vtable + env: + NODE_OPTIONS: '--max_old_space_size=4096' + run: node common/scripts/install-run-rush.js build --only @visactor/lark-vtable - - name: Publish to npm + - name: Build wx-vtable env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: node common/scripts/install-run-rush.js publish --publish --include-all + NODE_OPTIONS: '--max_old_space_size=4096' + run: node common/scripts/install-run-rush.js build --only @visactor/wx-vtable + + - name: Publish to npm (release) + if: startsWith(github.ref_name, 'release/') + run: node common/scripts/install-run-rush.js publish --publish --include-all --tag latest - name: Update shrinkwrap run: node common/scripts/install-run-rush.js update - - name: Get npm version - id: package-version - uses: xile611/read-package-version-action@v2.1 + - name: Get npm version (release) + if: startsWith(github.ref_name, 'release/') + id: package_version_release + uses: xile611/read-package-version-action@main with: path: packages/vtable - - name: Commit and push changes - uses: actions-js/push@master + - name: Commit & Push changes (release) + if: startsWith(github.ref_name, 'release/') + run: | + set -euo pipefail + if git diff --quiet; then + echo 'No changes to commit for release branch.' + else + git add . + git commit -m "build: release version ${{ steps.package_version_release.outputs.current_version }} [skip ci]" -n + git push --no-verify origin ${{ github.ref_name }} + fi + + - name: Create Pull Request to main (release) + if: startsWith(github.ref_name, 'release/') + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + BRANCH="${GITHUB_REF_NAME}" + echo "Source branch: $BRANCH" + # Check if PR already exists + if gh pr list --base main --head "$BRANCH" --state open --json number --limit 1 | grep -q '"number"'; then + echo "PR from $BRANCH to main already exists, skip creating."; + exit 0; + fi + TITLE="[Auto release] release ${{ steps.package_version_release.outputs.current_version }}" + BODY="This PR merges release branch $BRANCH into main." + gh pr create --base main --head "$BRANCH" --title "$TITLE" --body "$BODY" + + # ===== Hotfix flow ===== + - name: Parse semver (hotfix) + if: startsWith(github.ref_name, 'hotfix/') + id: semver_hotfix + uses: xile611/read-package-version-action@main with: - github_token: ${{ secrets.GITHUB_TOKEN }} - message: 'build: prelease version ${{ steps.package-version.outputs.current_version }}' - branch: ${{ github.ref_name }} + path: packages/vtable + semver_string: ${{ github.ref_name }} + semver_pattern: '^hotfix/(.*)$' - - name: Collect changelog of rush - uses: xile611/collect-rush-changlog@main - id: changelog + - name: update nextBump (hotfix) + if: startsWith(github.ref_name, 'hotfix/') + uses: xile611/set-next-bump-of-rush@main with: - version: ${{ steps.package-version.outputs.current_version }} + release_version: ${{ steps.semver_hotfix.outputs.full }} + write_next_bump: true + + - name: Generate rush version (hotfix) + if: startsWith(github.ref_name, 'hotfix/') + run: node common/scripts/install-run-rush.js version --bump + + - name: Update version (hotfix) + if: startsWith(github.ref_name, 'hotfix/') + run: node common/scripts/apply-release-version.js 'none' ${{ steps.semver_hotfix.outputs.main }} + + - name: Publish to npm (hotfix) + if: startsWith(github.ref_name, 'hotfix/') + run: node common/scripts/install-run-rush.js publish --publish --include-all --tag hotfix + + - name: Get npm version (hotfix) + if: startsWith(github.ref_name, 'hotfix/') + id: package_version_hotfix + uses: xile611/read-package-version-action@main + with: + path: packages/vtable + + - name: Commit & Push changes (hotfix) + if: startsWith(github.ref_name, 'hotfix/') + run: | + set -euo pipefail + if git diff --quiet; then + echo 'No changes to commit for hotfix branch.' + else + git add . + git commit -m "build: hotfix version ${{ steps.package_version_hotfix.outputs.current_version }} [skip ci]" -n + git push --no-verify origin ${{ github.ref_name }} + fi - - name: Create Pull Request - uses: dustinirving/create-pr@v1.0.2 + # ===== Pre-release flow ===== + - name: Parse semver (pre-release) + if: startsWith(github.ref_name, 'pre-release/') + id: semver_prerelease + uses: xile611/read-package-version-action@main with: - token: ${{ secrets.GITHUB_TOKEN }} - title: '[Auto release] release ${{ steps.package-version.outputs.current_version }}' - base: main - head: ${{ github.ref_name }} - labels: release - reviewers: fangsmile,Rui-Sun - body: | - ${{ steps.changelog.outputs.markdown }} + path: packages/vtable + semver_string: ${{ github.ref_name }} + semver_pattern: '^pre-release/(.*)$' + + - name: Apply prereleaseName (pre-release) + if: startsWith(github.ref_name, 'pre-release/') + run: node common/scripts/apply-release-version.js ${{ steps.semver_prerelease.outputs.pre_release_name }} ${{ steps.semver_prerelease.outputs.main }} + + - name: Publish to npm (pre-release) + if: startsWith(github.ref_name, 'pre-release/') + run: node common/scripts/install-run-rush.js publish --publish --include-all --tag ${{ steps.semver_prerelease.outputs.pre_release_type }} + + - name: Get npm version (pre-release) + if: startsWith(github.ref_name, 'pre-release/') + id: package_version_prerelease + uses: xile611/read-package-version-action@main + with: + path: packages/vtable + + - name: Commit & Push changes (pre-release) + if: startsWith(github.ref_name, 'pre-release/') + run: | + set -euo pipefail + if git diff --quiet; then + echo 'No changes to commit for pre-release branch.' + else + git add . + git commit -m "build: prerelease version ${{ steps.package_version_prerelease.outputs.current_version }} [skip ci]" -n + git push --no-verify origin ${{ github.ref_name }} + fi diff --git a/.github/workflows/sync-main-to-develop.yml b/.github/workflows/sync-main-to-develop.yml index 067c77eb1..825972cdf 100644 --- a/.github/workflows/sync-main-to-develop.yml +++ b/.github/workflows/sync-main-to-develop.yml @@ -1,32 +1,121 @@ name: Sync main to develop after release on: - push: + pull_request: + types: + - closed branches: - main jobs: sync_main_to_develop: - if: contains(github.event.head_commit.message, 'prelease version') - runs-on: ubuntu-latest + if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') + + runs-on: macos-latest + permissions: contents: write pull-requests: write + strategy: + matrix: + node-version: [20.x] + steps: - - name: Checkout main + - name: Checkout uses: actions/checkout@v4 + + - name: Configure Git identity + run: | + git config user.name ${{ github.actor }} + git config user.email ${{ github.actor }}@users.noreply.github.com + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + cache-dependency-path: './common/config/rush/pnpm-lock.yaml' + + - name: Install Python distutils (macOS) + if: runner.os == 'macOS' + run: | + python3 -m pip install setuptools --break-system-packages + + - name: Install Python distutils (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y python3-distutils + python3 -m pip install setuptools --break-system-packages + + - name: Install native deps for node-canvas (macOS) + if: runner.os == 'macOS' + run: | + brew update + brew install pkg-config cairo pango libpng jpeg giflib librsvg + + - name: Install native deps for node-canvas (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev pkg-config + + - name: Install latest pnpm + run: npm install -g pnpm@10.7.0 + + - name: Install rush + run: node common/scripts/install-run-rush.js install --bypass-policy + + - name: Read vtable version from package.json + id: package_version + uses: xile611/read-package-version-action@main with: - ref: main + path: packages/vtable + + - name: Compute sync branch name and check existence + id: sync_branch + run: | + set -euo pipefail + VERSION="${{ steps.package_version.outputs.current_version }}" + BRANCH="sync/main-${VERSION}" + echo "sync_branch=${BRANCH}" >> "$GITHUB_OUTPUT" - - name: Create PR from main to develop + git fetch origin "${BRANCH}":refs/remotes/origin/tmp-sync-branch 2>/dev/null || true + if git ls-remote --exit-code --heads origin "${BRANCH}" > /dev/null 2>&1; then + echo "Branch ${BRANCH} already exists on origin, skip creating new sync branch." + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Create and push sync/main-X.Y.Z branch + if: steps.sync_branch.outputs.exists == 'false' + run: | + set -euo pipefail + VERSION="${{ steps.package_version.outputs.current_version }}" + BRANCH="${{ steps.sync_branch.outputs.sync_branch }}" + git checkout main + git pull --ff-only origin main + git checkout -b "${BRANCH}" + git push --no-verify origin "${BRANCH}" + + - name: Create Pull Request to develop + if: steps.sync_branch.outputs.exists == 'false' env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - gh pr create \ - -B develop \ - -H main \ - -t "Sync main -> develop" \ - -b "Sync after release ${GITHUB_SHA}" \ - || echo 'PR may already exist' + BRANCH="${{ steps.sync_branch.outputs.sync_branch }}" + echo "Source branch: $BRANCH" + + # Check if PR already exists + if gh pr list --base develop --head "$BRANCH" --state open --json number --limit 1 | grep -q '"number"'; then + echo "PR from $BRANCH to develop already exists, skip creating." + exit 0 + fi + + TITLE="[Auto Sync] Sync the code from branch main to branch develop after release ${{ steps.package_version.outputs.current_version }}" + BODY="Sync the code from branch main to branch develop after release ${{ steps.package_version.outputs.current_version }}" + + gh pr create --base develop --head "$BRANCH" --title "$TITLE" --body "$BODY" --reviewer xuefei1313