Manual Release #173
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: Manual Release | |
| # Retry-safe: if a previous release failed after version bump, re-running | |
| # with the same parameters will detect the existing version bump and skip it. | |
| # The publish script handles already-published packages gracefully. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| branch: | |
| description: 'Branch to release from' | |
| required: true | |
| default: 'next' | |
| type: string | |
| version_type: | |
| description: 'Version bump type' | |
| required: true | |
| default: 'prerelease' | |
| type: choice | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| - prerelease | |
| - prepatch | |
| - preminor | |
| - premajor | |
| prerelease_tag: | |
| description: 'Prerelease tag (alpha, beta, rc) - only used with prerelease/pre* types' | |
| required: false | |
| default: 'beta' | |
| type: choice | |
| options: | |
| - alpha | |
| - beta | |
| - rc | |
| dry_run: | |
| description: 'Dry run (do not publish)' | |
| required: false | |
| default: false | |
| type: boolean | |
| jobs: | |
| release: | |
| name: Manual Release | |
| runs-on: ubuntu-latest | |
| concurrency: | |
| group: release-${{ github.event.inputs.branch }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| packages: write | |
| id-token: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 | |
| with: | |
| ref: ${{ github.event.inputs.branch }} | |
| fetch-depth: 0 | |
| token: ${{ secrets.GIT_TOKEN }} | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6 | |
| with: | |
| node-version: '24' | |
| cache: 'pnpm' | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Configure Git | |
| run: | | |
| git config --local user.email "action@github.com" | |
| git config --local user.name "GitHub Action" | |
| # Detect if this is a retry of a previously failed release. | |
| # Skips version bump if the commit/tag already exist on HEAD. | |
| - name: Detect existing release | |
| id: detect | |
| if: ${{ github.event.inputs.dry_run != 'true' }} | |
| run: | | |
| # Case 1: HEAD already has a version tag (version bump + push fully succeeded) | |
| EXISTING_TAG=$(git describe --tags --exact-match HEAD 2>/dev/null || true) | |
| if [[ -n "$EXISTING_TAG" && "$EXISTING_TAG" == v* ]]; then | |
| echo "🔄 Retry detected: found tag $EXISTING_TAG on HEAD" | |
| echo "skip_version_bump=true" >> $GITHUB_OUTPUT | |
| echo "need_push=false" >> $GITHUB_OUTPUT | |
| echo "NEW_TAG=$EXISTING_TAG" >> $GITHUB_ENV | |
| exit 0 | |
| fi | |
| # Case 2: HEAD is a version bump commit but tag may be missing (partial push) | |
| HEAD_MSG=$(git log -1 --format=%s HEAD) | |
| if [[ "$HEAD_MSG" == chore\(release\):*version\ bump ]]; then | |
| EGG_VERSION=$(node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('./packages/egg/package.json','utf8')).version)") | |
| EXPECTED_TAG="v${EGG_VERSION}" | |
| echo "🔄 Retry detected: version bump commit found (tag: $EXPECTED_TAG)" | |
| # Create the tag locally if it doesn't exist | |
| if ! git rev-parse "$EXPECTED_TAG" >/dev/null 2>&1; then | |
| echo " Creating missing tag: $EXPECTED_TAG" | |
| git tag "$EXPECTED_TAG" | |
| fi | |
| echo "skip_version_bump=true" >> $GITHUB_OUTPUT | |
| echo "need_push=true" >> $GITHUB_OUTPUT | |
| echo "NEW_TAG=$EXPECTED_TAG" >> $GITHUB_ENV | |
| exit 0 | |
| fi | |
| # Case 3: Fresh release | |
| echo "Fresh release: will perform version bump" | |
| echo "skip_version_bump=false" >> $GITHUB_OUTPUT | |
| echo "need_push=true" >> $GITHUB_OUTPUT | |
| - name: Version bump (dry run) | |
| if: ${{ github.event.inputs.dry_run == 'true' }} | |
| run: | | |
| echo "🧪 Running version bump in dry-run mode..." | |
| if [[ "${{ github.event.inputs.version_type }}" == pre* ]]; then | |
| node scripts/version.js ${{ github.event.inputs.version_type }} --prerelease-tag=${{ github.event.inputs.prerelease_tag }} --dry-run | |
| else | |
| node scripts/version.js ${{ github.event.inputs.version_type }} --dry-run | |
| fi | |
| - name: Version bump | |
| if: ${{ github.event.inputs.dry_run != 'true' && steps.detect.outputs.skip_version_bump != 'true' }} | |
| run: | | |
| echo "🚀 Running version bump..." | |
| if [[ "${{ github.event.inputs.version_type }}" == pre* ]]; then | |
| node scripts/version.js ${{ github.event.inputs.version_type }} --prerelease-tag=${{ github.event.inputs.prerelease_tag }} | |
| else | |
| node scripts/version.js ${{ github.event.inputs.version_type }} | |
| fi | |
| NEW_TAG=$(git describe --tags --abbrev=0) | |
| echo "NEW_TAG=$NEW_TAG" >> $GITHUB_ENV | |
| - name: Push version commit and tags | |
| if: ${{ github.event.inputs.dry_run != 'true' && steps.detect.outputs.need_push != 'false' }} | |
| run: | | |
| echo "📤 Pushing to origin/${{ github.event.inputs.branch }} with tags..." | |
| git push origin ${{ github.event.inputs.branch }} --tags | |
| - name: Run build | |
| run: pnpm build | |
| - name: Publish packages (dry run) | |
| if: ${{ github.event.inputs.dry_run == 'true' }} | |
| run: | | |
| if [[ "${{ github.event.inputs.version_type }}" == pre* ]]; then | |
| node scripts/publish.js --tag=${{ github.event.inputs.prerelease_tag }} --dry-run | |
| else | |
| node scripts/publish.js --tag=latest --dry-run | |
| fi | |
| - name: Publish packages | |
| if: ${{ github.event.inputs.dry_run != 'true' }} | |
| run: | | |
| if [[ "${{ github.event.inputs.version_type }}" == pre* ]]; then | |
| node scripts/publish.js --tag=${{ github.event.inputs.prerelease_tag }} --provenance | |
| else | |
| node scripts/publish.js --tag=latest --provenance | |
| fi | |
| - name: Create GitHub Release (draft) | |
| if: ${{ !cancelled() && github.event.inputs.dry_run != 'true' && env.NEW_TAG != '' }} | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 | |
| with: | |
| github-token: ${{ secrets.GIT_TOKEN }} | |
| script: | | |
| const tag = process.env.NEW_TAG; | |
| const versionType = '${{ github.event.inputs.version_type }}'; | |
| // Idempotent: check if release already exists (safe for retry) | |
| try { | |
| const existing = await github.rest.repos.getReleaseByTag({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| tag: tag, | |
| }); | |
| core.info(`Release already exists: ${existing.data.html_url}`); | |
| core.exportVariable('DRAFT_RELEASE_URL', existing.data.html_url); | |
| return; | |
| } catch (e) { | |
| if (e.status !== 404) throw e; | |
| // 404 = no release exists, proceed to create | |
| } | |
| let releaseBody = `## 🎉 ${versionType.charAt(0).toUpperCase() + versionType.slice(1)} Release\n\n`; | |
| releaseBody += `This release includes ${versionType} version updates for all packages.\n\n`; | |
| releaseBody += `### 📦 Published Packages\n\n`; | |
| const fs = require('fs'); | |
| const packagesDirs = ['./packages', './tools', './plugins', './tegg/core', './tegg/plugin', './tegg/standalone']; | |
| for (const packagesDir of packagesDirs) { | |
| if (!fs.existsSync(packagesDir)) continue; | |
| const packageFolders = fs.readdirSync(packagesDir); | |
| for (const folder of packageFolders) { | |
| const packageJsonPath = `${packagesDir}/${folder}/package.json`; | |
| if (fs.existsSync(packageJsonPath)) { | |
| const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); | |
| if (packageJson.private) continue; | |
| releaseBody += `- [${packageJson.name}@${packageJson.version}](https://npmjs.com/package/${packageJson.name}/v/${packageJson.version})\n`; | |
| } | |
| } | |
| } | |
| releaseBody += `\n### 🔄 What's Changed\n\n`; | |
| releaseBody += `<!-- Please add changelog information here manually -->\n`; | |
| releaseBody += `- Add your changelog items here\n`; | |
| releaseBody += `- Remove this placeholder text\n\n`; | |
| releaseBody += `**Full Changelog**: https://github.com/${{ github.repository }}/compare/v${tag}...${tag}`; | |
| const release = await github.rest.repos.createRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| tag_name: tag, | |
| name: tag, | |
| body: releaseBody, | |
| draft: true, | |
| prerelease: versionType.includes('pre') | |
| }); | |
| core.info(`Created draft release: ${release.data.html_url}`); | |
| core.exportVariable('DRAFT_RELEASE_URL', release.data.html_url); | |
| - name: Sync to cnpm | |
| if: ${{ !cancelled() && github.event.inputs.dry_run != 'true' }} | |
| run: node scripts/sync-cnpm.js | |
| - name: Summary | |
| if: ${{ !cancelled() }} | |
| run: | | |
| echo "## 🎉 Release Summary" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then | |
| echo "### 🧪 Dry Run Completed" >> $GITHUB_STEP_SUMMARY | |
| echo "- Version bump: **${{ github.event.inputs.version_type }}**" >> $GITHUB_STEP_SUMMARY | |
| echo "- Branch: **${{ github.event.inputs.branch }}**" >> $GITHUB_STEP_SUMMARY | |
| if [[ "${{ github.event.inputs.version_type }}" == pre* ]]; then | |
| echo "- npm tag: **${{ github.event.inputs.prerelease_tag }}**" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "- npm tag: **latest**" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "- Status: **Dry run - no changes made**" >> $GITHUB_STEP_SUMMARY | |
| else | |
| if [ "${{ steps.detect.outputs.skip_version_bump }}" == "true" ]; then | |
| echo "### 🔄 Retry Release Completed" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "### ✅ Release Completed" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "- Version bump: **${{ github.event.inputs.version_type }}**" >> $GITHUB_STEP_SUMMARY | |
| echo "- Branch: **${{ github.event.inputs.branch }}**" >> $GITHUB_STEP_SUMMARY | |
| echo "- Tag: **$NEW_TAG**" >> $GITHUB_STEP_SUMMARY | |
| if [[ "${{ github.event.inputs.version_type }}" == pre* ]]; then | |
| echo "- npm tag: **${{ github.event.inputs.prerelease_tag }}**" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "- npm tag: **latest**" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "- Packages published to npm" >> $GITHUB_STEP_SUMMARY | |
| if [ -n "$DRAFT_RELEASE_URL" ]; then | |
| echo "- GitHub Release: [View Draft]($DRAFT_RELEASE_URL)" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| fi |