Publish Packages #68
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: Publish Packages | |
| on: | |
| push: | |
| branches: [master] | |
| paths: | |
| - "packages/*/package.json" | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| id-token: write | |
| env: | |
| NODE_VERSION: "22" | |
| jobs: | |
| # ========================================================================= | |
| # GATE — only publish from release-prepare workflow (release/* branch merges) | |
| # ========================================================================= | |
| release-gate: | |
| name: 🔒 Release Gate | |
| runs-on: ubuntu-latest | |
| outputs: | |
| is-release: ${{ steps.check.outputs.is-release }} | |
| steps: | |
| - name: 📥 Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 5 | |
| - name: 🔒 Check if this is a release commit | |
| id: check | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # For workflow_dispatch, always allow | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "is-release=true" >> $GITHUB_OUTPUT | |
| echo "✅ Manual trigger — allowed" | |
| exit 0 | |
| fi | |
| # Check if the merge commit came from a release/* branch | |
| COMMIT_MSG=$(git log -1 --pretty=%s) | |
| echo "Commit message: $COMMIT_MSG" | |
| # Match: merge commits from release/*, squash merges (PR title "Release ..."), or release prepare commits | |
| if echo "$COMMIT_MSG" | grep -qE "(^Merge pull request .* from .*/release/|^Release |^chore: prepare release)"; then | |
| echo "is-release=true" >> $GITHUB_OUTPUT | |
| echo "✅ Release commit detected — proceeding with publish" | |
| else | |
| echo "is-release=false" >> $GITHUB_OUTPUT | |
| echo "⛔ Not a release commit — skipping publish" | |
| echo "💡 All releases must go through the release-prepare workflow" | |
| fi | |
| detect-changes: | |
| name: 🔍 Detect Version Changes | |
| needs: [release-gate] | |
| if: needs.release-gate.outputs.is-release == 'true' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| packages: ${{ steps.detect.outputs.packages }} | |
| has-changes: ${{ steps.detect.outputs.has-changes }} | |
| steps: | |
| - name: 📥 Checkout code | |
| uses: actions/checkout@v6 | |
| - name: 🔧 Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: "npm" | |
| - name: 📦 Install dependencies | |
| run: npm ci | |
| - name: 🔍 Detect changed package versions | |
| id: detect | |
| run: | | |
| # Function to get package info | |
| get_package_info() { | |
| local pkg_dir=$1 | |
| local pkg_json="${pkg_dir}/package.json" | |
| if [ ! -f "$pkg_json" ]; then | |
| return | |
| fi | |
| local name=$(node -p "require('./${pkg_json}').name") | |
| local version=$(node -p "require('./${pkg_json}').version") | |
| local private=$(node -p "require('./${pkg_json}').private || false") | |
| # Skip private packages | |
| if [ "$private" = "true" ]; then | |
| return | |
| fi | |
| echo "${pkg_dir}|${name}|${version}" | |
| } | |
| # Function to check if version exists on npm | |
| version_exists_on_npm() { | |
| local name=$1 | |
| local version=$2 | |
| # Try to get the specific version from npm | |
| if npm view "${name}@${version}" version 2>/dev/null; then | |
| return 0 # Version exists | |
| else | |
| return 1 # Version doesn't exist | |
| fi | |
| } | |
| changed_packages=() | |
| # Check packages in dependency order (core first, then platform) | |
| for pkg_dir in packages/core packages/platform; do | |
| if [ ! -d "$pkg_dir" ]; then | |
| continue | |
| fi | |
| pkg_info=$(get_package_info "$pkg_dir") | |
| if [ -z "$pkg_info" ]; then | |
| continue | |
| fi | |
| IFS='|' read -r dir name version <<< "$pkg_info" | |
| echo "📦 Checking ${name}@${version}..." | |
| # Check if this version exists on npm | |
| if version_exists_on_npm "$name" "$version"; then | |
| echo "⏭️ Version ${version} already published for ${name}" | |
| else | |
| echo "✨ New version detected: ${name}@${version}" | |
| changed_packages+=("{\"name\":\"${name}\",\"version\":\"${version}\",\"dir\":\"${dir}\"}") | |
| fi | |
| done | |
| # Build JSON array | |
| if [ ${#changed_packages[@]} -eq 0 ]; then | |
| echo "has-changes=false" >> $GITHUB_OUTPUT | |
| echo "packages=[]" >> $GITHUB_OUTPUT | |
| echo "ℹ️ No version changes detected" | |
| else | |
| echo "has-changes=true" >> $GITHUB_OUTPUT | |
| packages_json=$(printf '%s\n' "${changed_packages[@]}" | jq -s -c '.') | |
| echo "packages=${packages_json}" >> $GITHUB_OUTPUT | |
| echo "🎯 Packages to publish:" | |
| echo "${packages_json}" | jq '.' | |
| fi | |
| verify: | |
| name: ✅ Verify All Packages | |
| needs: [detect-changes] | |
| if: needs.detect-changes.outputs.has-changes == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: 📥 Checkout code | |
| uses: actions/checkout@v6 | |
| - name: 🔧 Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: "npm" | |
| - name: 📦 Install dependencies | |
| run: npm ci | |
| - name: 🔍 Type check | |
| run: npm run type-check | |
| - name: 🎨 Lint | |
| run: npm run lint | |
| - name: 🎨 Format check | |
| run: npm run format:check | |
| - name: 🧪 Test | |
| run: npm run test | |
| - name: 🏗️ Build | |
| run: npm run build | |
| - name: ✅ All checks passed | |
| run: echo "✅ All packages verified successfully" >> $GITHUB_STEP_SUMMARY | |
| publish: | |
| name: 📤 Publish ${{ matrix.package.name }}@${{ matrix.package.version }} | |
| needs: [detect-changes, verify] | |
| if: needs.detect-changes.outputs.has-changes == 'true' && needs.verify.result == 'success' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| package: ${{ fromJson(needs.detect-changes.outputs.packages) }} | |
| max-parallel: 1 | |
| fail-fast: true | |
| steps: | |
| - name: 📥 Checkout code | |
| uses: actions/checkout@v6 | |
| - name: 🔧 Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: "npm" | |
| registry-url: "https://registry.npmjs.org" | |
| - name: 📦 Install dependencies | |
| run: npm ci | |
| - name: 🏗️ Build all workspace packages | |
| run: npm run build | |
| - name: 📤 Publish to npm | |
| working-directory: ${{ matrix.package.dir }} | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| echo "📤 Publishing ${{ matrix.package.name }}@${{ matrix.package.version }}" | |
| npm publish --access public | |
| - name: 🏷️ Create git tag | |
| run: | | |
| git config --global user.name "github-actions[bot]" | |
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | |
| # Extract package name without scope | |
| PKG_SHORT_NAME=$(echo "${{ matrix.package.name }}" | sed 's/@agentage\///') | |
| TAG_NAME="${PKG_SHORT_NAME}@${{ matrix.package.version }}" | |
| git tag -a "${TAG_NAME}" -m "Release ${{ matrix.package.name }}@${{ matrix.package.version }}" | |
| git push origin "${TAG_NAME}" | |
| - name: ✅ Published successfully | |
| run: | | |
| echo "🎉 Successfully published ${{ matrix.package.name }}@${{ matrix.package.version }}" >> $GITHUB_STEP_SUMMARY | |
| echo "📦 Package URL: https://www.npmjs.com/package/${{ matrix.package.name }}/v/${{ matrix.package.version }}" >> $GITHUB_STEP_SUMMARY | |
| summary: | |
| name: 📋 Publish Summary | |
| needs: [release-gate, detect-changes, verify, publish] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: 📊 Create summary | |
| run: | | |
| echo "## 📦 Package Publishing Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ needs.release-gate.outputs.is-release }}" != "true" ]; then | |
| echo "⛔ **Blocked — not a release commit**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "All releases must go through the \`release-prepare\` workflow." >> $GITHUB_STEP_SUMMARY | |
| echo "Run: \`gh workflow run release-prepare.yml -f bump_type=patch -f packages=core,platform\`" >> $GITHUB_STEP_SUMMARY | |
| exit 0 | |
| elif [ "${{ needs.verify.result }}" != "success" ]; then | |
| echo "❌ **Verification failed - packages not published**" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Please fix verification errors and try again." >> $GITHUB_STEP_SUMMARY | |
| elif [ "${{ needs.publish.result }}" == "success" ]; then | |
| echo "✅ **All packages published successfully!**" >> $GITHUB_STEP_SUMMARY | |
| elif [ "${{ needs.publish.result }}" == "skipped" ]; then | |
| echo "⏭️ **Publishing skipped - verification failed**" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "⚠️ **Some packages failed to publish**" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Packages" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| packages='${{ needs.detect-changes.outputs.packages }}' | |
| if [ "${{ needs.verify.result }}" == "success" ] && [ "${{ needs.publish.result }}" == "success" ]; then | |
| echo "$packages" | jq -r '.[] | "- ✅ **\(.name)** version \(.version) - Published"' >> $GITHUB_STEP_SUMMARY | |
| elif [ "${{ needs.verify.result }}" != "success" ]; then | |
| echo "$packages" | jq -r '.[] | "- ❌ **\(.name)** version \(.version) - Verification failed"' >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "$packages" | jq -r '.[] | "- ⏭️ **\(.name)** version \(.version) - Skipped"' >> $GITHUB_STEP_SUMMARY | |
| fi |