Merge pull request #3197 from Maltronic/patch-1 #139
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: Production Package Publishing | |
| on: | |
| push: | |
| branches: [main] | |
| paths-ignore: | |
| - '**/package-lock.json' | |
| - '**/yarn.lock' | |
| jobs: | |
| check-automation: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should-skip: ${{ steps.check-automation.outputs.should-skip }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check if triggered by automation | |
| id: check-automation | |
| run: | | |
| # Skip if actor is github-actions or dependabot | |
| if [[ "${{ github.actor }}" == "github-actions[bot]" ]] || [[ "${{ github.actor }}" == "dependabot[bot]" ]] || [[ "${{ github.actor }}" == "github-actions" ]]; then | |
| echo "should-skip=true" >> $GITHUB_OUTPUT | |
| echo "🚫 Skipping workflow: triggered by automated actor (${{ github.actor }})" | |
| exit 0 | |
| fi | |
| # Get the latest commit message and author | |
| COMMIT_MESSAGE=$(git log -1 --pretty=%B) | |
| COMMIT_AUTHOR=$(git log -1 --pretty=%an) | |
| echo "Commit message: $COMMIT_MESSAGE" | |
| echo "Commit author: $COMMIT_AUTHOR" | |
| # Skip if commit message contains [skip ci] or [ci skip] | |
| if echo "$COMMIT_MESSAGE" | grep -q "\[skip ci\]"; then | |
| echo "should-skip=true" >> $GITHUB_OUTPUT | |
| echo "🚫 Skipping workflow: [skip ci] found in commit message" | |
| exit 0 | |
| fi | |
| if echo "$COMMIT_MESSAGE" | grep -q "\[ci skip\]"; then | |
| echo "should-skip=true" >> $GITHUB_OUTPUT | |
| echo "🚫 Skipping workflow: [ci skip] found in commit message" | |
| exit 0 | |
| fi | |
| # Skip if commit author is automated | |
| if [[ "$COMMIT_AUTHOR" == *"github-actions"* ]] || [[ "$COMMIT_AUTHOR" == *"GitHub Action"* ]] || [[ "$COMMIT_AUTHOR" == *"dependabot"* ]]; then | |
| echo "should-skip=true" >> $GITHUB_OUTPUT | |
| echo "🚫 Skipping workflow: commit by automated author ($COMMIT_AUTHOR)" | |
| exit 0 | |
| fi | |
| # Skip if commit message starts with automated version update pattern | |
| if echo "$COMMIT_MESSAGE" | grep -q "^chore: update package versions to"; then | |
| echo "should-skip=true" >> $GITHUB_OUTPUT | |
| echo "🚫 Skipping workflow: automated version update commit" | |
| exit 0 | |
| fi | |
| echo "should-skip=false" >> $GITHUB_OUTPUT | |
| echo "✅ Proceeding with workflow" | |
| check-changesets: | |
| needs: check-automation | |
| if: needs.check-automation.outputs.should-skip != 'true' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| has-changesets: ${{ steps.check-changesets.outputs.has-changesets }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check for changesets | |
| id: check-changesets | |
| run: | | |
| if [ -d ".changeset" ] && [ "$(ls -A .changeset/*.md 2>/dev/null | wc -l)" -gt 0 ]; then | |
| echo "has-changesets=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has-changesets=false" >> $GITHUB_OUTPUT | |
| fi | |
| publish-production: | |
| needs: [check-automation, check-changesets] | |
| if: needs.check-automation.outputs.should-skip != 'true' && needs.check-changesets.outputs.has-changesets == 'true' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| published: ${{ steps.publish.outputs.published }} | |
| version: ${{ steps.publish.outputs.version }} | |
| packages: ${{ steps.publish.outputs.packages }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'yarn' | |
| - name: Install dependencies | |
| run: yarn install --frozen-lockfile | |
| - name: Setup NPM authentication | |
| run: | | |
| echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc | |
| - name: Version packages with changesets | |
| id: version | |
| run: | | |
| set -e # Exit on any error | |
| set -o pipefail # Exit on pipeline errors | |
| # Check if there are changesets to consume | |
| if [ -d ".changeset" ] && [ "$(ls -A .changeset/*.md 2>/dev/null | wc -l)" -gt 0 ]; then | |
| echo "📝 Consuming changesets..." | |
| yarn changeset:version | |
| # Check if any package.json was modified | |
| if git diff --quiet; then | |
| echo "No version changes needed" | |
| echo "version_changed=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "Version changes detected" | |
| echo "version_changed=true" >> $GITHUB_OUTPUT | |
| # Get the new version from the packages that were actually modified by changesets | |
| MODIFIED_PACKAGES=$(git diff --name-only HEAD~1 | grep "packages/.*/package.json" || true) | |
| if [ -z "$MODIFIED_PACKAGES" ]; then | |
| echo "No modified package.json files found, falling back to first non-private package" | |
| NEW_VERSION=$(node -e " | |
| const fs = require('fs'); | |
| const packages = fs.readdirSync('packages'); | |
| for (const pkg of packages) { | |
| try { | |
| const pkgJson = JSON.parse(fs.readFileSync(\`packages/\${pkg}/package.json\`, 'utf8')); | |
| if (!pkgJson.private) { | |
| console.log(pkgJson.version); | |
| break; | |
| } | |
| } catch (e) {} | |
| } | |
| ") | |
| else | |
| echo "📋 Getting version from modified packages: $MODIFIED_PACKAGES" | |
| # Get version from the first modified package | |
| FIRST_MODIFIED=$(echo "$MODIFIED_PACKAGES" | head -n1) | |
| NEW_VERSION=$(node -e " | |
| try { | |
| const pkg = JSON.parse(require('fs').readFileSync('$FIRST_MODIFIED', 'utf8')); | |
| console.log(pkg.version || ''); | |
| } catch(e) { console.log(''); } | |
| ") | |
| fi | |
| echo "📋 Using version: $NEW_VERSION" | |
| echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| echo "No changesets found, skipping versioning" | |
| echo "version_changed=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Get packages to publish | |
| id: packages-to-publish | |
| if: steps.version.outputs.version_changed == 'true' | |
| run: | | |
| set -e # Exit on any error | |
| set -o pipefail # Exit on pipeline errors | |
| echo "🔍 Identifying packages that need to be published..." | |
| # Get list of modified package.json files (these are the ones that were versioned by changesets) | |
| MODIFIED_PACKAGES=$(git diff --name-only HEAD~1 | grep "packages/.*/package.json" || true) | |
| if [ -z "$MODIFIED_PACKAGES" ]; then | |
| echo "❌ CRITICAL: No modified package.json files found" | |
| echo "::error::No packages were modified by changesets" | |
| echo "packages_to_publish=" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| echo " Modified packages:" | |
| echo "$MODIFIED_PACKAGES" | |
| # Extract package directories and names | |
| PACKAGE_DIRS="" | |
| PACKAGE_NAMES="" | |
| while IFS= read -r pkg_file; do | |
| if [ -n "$pkg_file" ]; then | |
| PACKAGE_DIR=$(dirname "$pkg_file") | |
| PACKAGE_NAME=$(node -e " | |
| try { | |
| const pkg = JSON.parse(require('fs').readFileSync('$pkg_file', 'utf8')); | |
| console.log(pkg.name || ''); | |
| } catch(e) { console.log(''); } | |
| ") | |
| if [ -n "$PACKAGE_NAME" ]; then | |
| PACKAGE_DIRS="$PACKAGE_DIRS $PACKAGE_DIR" | |
| PACKAGE_NAMES="$PACKAGE_NAMES $PACKAGE_NAME" | |
| echo "✅ Will publish: $PACKAGE_NAME from $PACKAGE_DIR" | |
| fi | |
| fi | |
| done <<< "$MODIFIED_PACKAGES" | |
| echo "packages_to_publish=$PACKAGE_NAMES" >> $GITHUB_OUTPUT | |
| echo "package_dirs=$PACKAGE_DIRS" >> $GITHUB_OUTPUT | |
| - name: Publish packages | |
| id: publish | |
| if: steps.version.outputs.version_changed == 'true' | |
| run: | | |
| set -e # Exit on any error | |
| set -o pipefail # Exit on pipeline errors | |
| echo "🚀 Publishing packages..." | |
| PUBLISHED_PACKAGES="" | |
| PACKAGE_DIRS="${{ steps.packages-to-publish.outputs.package_dirs }}" | |
| PACKAGE_NAMES="${{ steps.packages-to-publish.outputs.packages_to_publish }}" | |
| echo "📦 Packages to publish: $PACKAGE_NAMES" | |
| echo "📁 Package directories: $PACKAGE_DIRS" | |
| # Function to check if version exists on npm | |
| check_npm_version() { | |
| local package_name=$1 | |
| local version=$2 | |
| if npm view "$package_name@$version" version >/dev/null 2>&1; then | |
| echo "true" | |
| else | |
| echo "false" | |
| fi | |
| } | |
| # Function to get latest version from npm | |
| get_npm_latest_version() { | |
| local package_name=$1 | |
| local latest_version=$(npm view "$package_name" version 2>/dev/null || echo "0.0.0") | |
| echo "$latest_version" | |
| } | |
| # Function to increment version | |
| increment_version() { | |
| local version=$1 | |
| local increment_type=${2:-patch} | |
| node -e " | |
| const semver = require('semver'); | |
| const version = '$version'; | |
| const incrementType = '$increment_type'; | |
| console.log(semver.inc(version, incrementType)); | |
| " 2>/dev/null || node -e " | |
| const version = '$version'; | |
| const parts = version.split('.'); | |
| parts[2] = (parseInt(parts[2]) + 1).toString(); | |
| console.log(parts.join('.')); | |
| " | |
| } | |
| # Publish only the packages that were versioned by changesets | |
| for package_dir in $PACKAGE_DIRS; do | |
| if [ ! -d "$package_dir" ]; then | |
| echo "⚠️ Package directory not found: $package_dir" | |
| continue | |
| fi | |
| PACKAGE_JSON=$(cat "$package_dir/package.json") | |
| PACKAGE_NAME=$(echo "$PACKAGE_JSON" | node -e "console.log(JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8')).name)") | |
| IS_PRIVATE=$(echo "$PACKAGE_JSON" | node -e "console.log(JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8')).private || false)") | |
| if [ "$IS_PRIVATE" = "true" ]; then | |
| echo "⏭️ Skipping private package: $PACKAGE_NAME" | |
| continue | |
| fi | |
| echo "📦 Processing $PACKAGE_NAME from $package_dir..." | |
| cd "$package_dir" | |
| # Get current package version | |
| CURRENT_VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version)") | |
| echo "📋 Current version: $CURRENT_VERSION" | |
| # Check if version already exists on npm | |
| VERSION_EXISTS=$(check_npm_version "$PACKAGE_NAME" "$CURRENT_VERSION") | |
| if [ "$VERSION_EXISTS" = "true" ]; then | |
| echo "⚠️ Version $CURRENT_VERSION already exists on npm for $PACKAGE_NAME" | |
| echo "🔄 Checking latest version on npm..." | |
| NPM_LATEST=$(get_npm_latest_version "$PACKAGE_NAME") | |
| echo "📦 Latest version on npm: $NPM_LATEST" | |
| if [ "$CURRENT_VERSION" = "$NPM_LATEST" ]; then | |
| echo "📈 Incrementing patch version..." | |
| NEW_VERSION=$(increment_version "$CURRENT_VERSION" "patch") | |
| else | |
| echo "📈 Using next version after npm latest..." | |
| NEW_VERSION=$(increment_version "$NPM_LATEST" "patch") | |
| fi | |
| echo "🔄 Updating package version to $NEW_VERSION..." | |
| # Update package.json version | |
| node -e " | |
| const fs = require('fs'); | |
| const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); | |
| pkg.version = '$NEW_VERSION'; | |
| fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); | |
| " | |
| CURRENT_VERSION=$NEW_VERSION | |
| echo "✅ Updated to version: $CURRENT_VERSION" | |
| fi | |
| # Install utils dependency if this is core package | |
| if [[ "$PACKAGE_NAME" == *"core"* ]]; then | |
| echo "🎯 Installing utils package for core package..." | |
| echo "📦 Attempting to install latest utils package: @gluestack-ui/utils" | |
| # First try to install the latest public version | |
| if npm install @gluestack-ui/utils --no-save --registry https://registry.npmjs.org/ 2>&1; then | |
| echo "✅ Utils package installed successfully" | |
| # Show which version was installed | |
| INSTALLED_VERSION=$(node -e "try { console.log(require('./node_modules/@gluestack-ui/utils/package.json').version) } catch(e) { console.log('unknown') }") | |
| echo "📦 Installed utils version: $INSTALLED_VERSION" | |
| else | |
| echo "⚠️ Failed to install utils package, but continuing..." | |
| echo "🔍 Available versions:" | |
| npm view @gluestack-ui/utils versions --json 2>/dev/null || echo "Could not fetch available versions" | |
| fi | |
| # Verify utils package is available in node_modules | |
| if [ -d "./node_modules/@gluestack-ui/utils" ]; then | |
| echo "✅ Utils package verified in node_modules" | |
| else | |
| echo "⚠️ Utils package not found in node_modules, build may fail" | |
| fi | |
| fi | |
| # Build if build script exists | |
| if yarn run --list 2>/dev/null | grep -q "build"; then | |
| echo "🔨 Building $PACKAGE_NAME..." | |
| yarn build | |
| fi | |
| # Publish to latest tag | |
| echo "🚀 Publishing $PACKAGE_NAME@$CURRENT_VERSION..." | |
| if npm publish --access public; then | |
| echo "✅ Successfully published $PACKAGE_NAME@$CURRENT_VERSION" | |
| PUBLISHED_PACKAGES="$PUBLISHED_PACKAGES $PACKAGE_NAME@$CURRENT_VERSION" | |
| else | |
| PUBLISH_ERROR=$? | |
| echo "❌ Failed to publish $PACKAGE_NAME@$CURRENT_VERSION (exit code: $PUBLISH_ERROR)" | |
| if [ $PUBLISH_ERROR -eq 1 ]; then | |
| echo "🔄 Version conflict detected. Incrementing version and retrying..." | |
| NEW_VERSION=$(increment_version "$CURRENT_VERSION" "patch") | |
| node -e " | |
| const fs = require('fs'); | |
| const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); | |
| pkg.version = '$NEW_VERSION'; | |
| fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); | |
| " | |
| CURRENT_VERSION=$NEW_VERSION | |
| echo "🔄 Retrying publish with version $CURRENT_VERSION..." | |
| if npm publish --access public; then | |
| echo "✅ Successfully published $PACKAGE_NAME@$CURRENT_VERSION" | |
| PUBLISHED_PACKAGES="$PUBLISHED_PACKAGES $PACKAGE_NAME@$CURRENT_VERSION" | |
| else | |
| echo "❌ CRITICAL: Failed to publish $PACKAGE_NAME@$CURRENT_VERSION after retry" | |
| echo "::error::npm publish failed for $PACKAGE_NAME after retry" | |
| exit 1 | |
| fi | |
| else | |
| echo "❌ CRITICAL: Unexpected publish error for $PACKAGE_NAME" | |
| echo "::error::Unexpected npm publish error for $PACKAGE_NAME" | |
| exit 1 | |
| fi | |
| fi | |
| cd - > /dev/null | |
| done | |
| echo "published=true" >> $GITHUB_OUTPUT | |
| echo "packages=$PUBLISHED_PACKAGES" >> $GITHUB_OUTPUT | |
| # Update the version output to reflect the actual published version | |
| # Extract the version from the first published package | |
| if [ -n "$PUBLISHED_PACKAGES" ]; then | |
| ACTUAL_VERSION=$(echo "$PUBLISHED_PACKAGES" | tr ' ' '\n' | grep '@' | sed 's/.*@//' | head -n1) | |
| echo "📋 Setting actual published version: $ACTUAL_VERSION" | |
| echo "version=$ACTUAL_VERSION" >> $GITHUB_OUTPUT | |
| else | |
| echo "⚠️ No published packages found, keeping original version" | |
| fi | |
| - name: Update package versions in consuming projects | |
| if: steps.publish.outputs.published == 'true' | |
| run: | | |
| echo "🔄 Updating package versions in consuming projects..." | |
| PUBLISHED_VERSION="${{ steps.version.outputs.version }}" | |
| PUBLISHED_PACKAGES="${{ steps.publish.outputs.packages }}" | |
| # Extract package names from published packages | |
| PACKAGE_NAMES=$(echo "$PUBLISHED_PACKAGES" | sed 's/@[^ ]*//g') | |
| echo "📦 Published packages: $PACKAGE_NAMES" | |
| echo "📋 Published version: $PUBLISHED_VERSION" | |
| # Update package.json files in apps/ and other consuming directories | |
| find . -name "package.json" -not -path "*/node_modules/*" -not -path "./packages/*" | while read -r pkg_file; do | |
| echo "📝 Checking $pkg_file..." | |
| # Check if this package.json uses any of our published packages | |
| HAS_DEPENDENCIES=false | |
| for pkg_name in $PACKAGE_NAMES; do | |
| if grep -q "\"$pkg_name\"" "$pkg_file"; then | |
| HAS_DEPENDENCIES=true | |
| break | |
| fi | |
| done | |
| if [ "$HAS_DEPENDENCIES" = "true" ]; then | |
| echo "📝 Updating dependencies in $pkg_file..." | |
| # Update dependencies to use the new version | |
| node -e " | |
| const fs = require('fs'); | |
| const pkg = JSON.parse(fs.readFileSync('$pkg_file', 'utf8')); | |
| const newVersion = '$PUBLISHED_VERSION'; | |
| const packageNames = '$PACKAGE_NAMES'.split(' '); | |
| let changed = false; | |
| ['dependencies', 'devDependencies', 'peerDependencies'].forEach(depType => { | |
| if (pkg[depType]) { | |
| packageNames.forEach(pkgName => { | |
| if (pkg[depType][pkgName]) { | |
| console.log(\`Updating \${pkgName} from \${pkg[depType][pkgName]} to \${newVersion} in \${depType}\`); | |
| pkg[depType][pkgName] = newVersion; | |
| changed = true; | |
| } | |
| }); | |
| } | |
| }); | |
| if (changed) { | |
| fs.writeFileSync('$pkg_file', JSON.stringify(pkg, null, 2) + '\n'); | |
| console.log('✅ Updated $pkg_file'); | |
| } else { | |
| console.log('ℹ️ No updates needed in $pkg_file'); | |
| } | |
| " | |
| fi | |
| done | |
| - name: Create PR for version updates | |
| if: steps.publish.outputs.published == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} # Use PAT instead of default token | |
| script: | | |
| const { execSync } = require('child_process'); | |
| // Check if there are any changes to commit | |
| try { | |
| const status = execSync('git status --porcelain', { encoding: 'utf8' }); | |
| if (!status.trim()) { | |
| console.log('No changes to commit, skipping PR creation'); | |
| return; | |
| } | |
| } catch (e) { | |
| console.log('Error checking git status:', e.message); | |
| return; | |
| } | |
| // Create a new branch for the PR | |
| const branchName = `update-package-versions-${Date.now()}`; | |
| const commitMessage = `chore: update package versions to ${{ steps.version.outputs.version }} [skip ci]`; | |
| try { | |
| // Configure git | |
| execSync('git config --local user.email "[email protected]"'); | |
| execSync('git config --local user.name "GitHub Action"'); | |
| // Create new branch | |
| execSync(`git checkout -b ${branchName}`); | |
| // Add and commit changes | |
| execSync('git add .'); | |
| execSync(`git commit -m "${commitMessage}"`); | |
| // Push the branch | |
| execSync(`git push origin ${branchName}`); | |
| // Create PR | |
| const pr = await github.rest.pulls.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: `chore: update package versions to ${{ steps.version.outputs.version }}`, | |
| body: `## 📦 Package Version Updates | |
| This PR updates package versions in consuming projects to use the newly published packages. | |
| **Published packages:** | |
| ${{ steps.publish.outputs.packages }} | |
| **New version:** \`${{ steps.version.outputs.version }}\` | |
| > ⚠️ **Do not auto-merge this PR** - Review the changes before merging. | |
| > This PR was created automatically by the GitHub Actions workflow. | |
| `, | |
| head: branchName, | |
| base: 'main', | |
| draft: false | |
| }); | |
| console.log(`✅ Created PR #${pr.data.number}: ${pr.data.html_url}`); | |
| } catch (error) { | |
| console.error('Error creating PR:', error.message); | |
| } | |
| - name: Create GitHub Release | |
| if: steps.publish.outputs.published == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const { execSync } = require('child_process'); | |
| // Get latest tag | |
| let latestTag; | |
| try { | |
| latestTag = execSync('git describe --tags --abbrev=0', { encoding: 'utf8' }).trim(); | |
| } catch (e) { | |
| latestTag = ''; | |
| } | |
| // Generate changelog | |
| let changelog = '## Changes\n\n'; | |
| if (latestTag) { | |
| try { | |
| const commits = execSync(`git log ${latestTag}..HEAD --oneline --no-merges`, { encoding: 'utf8' }) | |
| .trim() | |
| .split('\n') | |
| .filter(line => line.length > 0) | |
| .map(line => `- ${line}`) | |
| .join('\n'); | |
| changelog += commits || 'No significant changes'; | |
| } catch (e) { | |
| changelog += 'Unable to generate changelog'; | |
| } | |
| } else { | |
| changelog += 'Initial release'; | |
| } | |
| // Check if release already exists and create or update accordingly | |
| const tagName = `v${{ steps.version.outputs.version }}`; | |
| const releaseBody = changelog + '\n\n**Published packages:**\n${{ steps.publish.outputs.packages }}'; | |
| try { | |
| // Try to get existing release | |
| const existingRelease = await github.rest.repos.getReleaseByTag({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| tag: tagName | |
| }); | |
| console.log(`Release ${tagName} already exists, updating it...`); | |
| // Update existing release | |
| await github.rest.repos.updateRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| release_id: existingRelease.data.id, | |
| name: tagName, | |
| body: releaseBody, | |
| draft: false, | |
| prerelease: false | |
| }); | |
| console.log(`✅ Updated existing release ${tagName}`); | |
| } catch (error) { | |
| if (error.status === 404) { | |
| // Release doesn't exist, create it | |
| console.log(`Creating new release ${tagName}...`); | |
| await github.rest.repos.createRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| tag_name: tagName, | |
| name: tagName, | |
| body: releaseBody, | |
| draft: false, | |
| prerelease: false | |
| }); | |
| console.log(`✅ Created new release ${tagName}`); | |
| } else { | |
| // Re-throw other errors | |
| throw error; | |
| } | |
| } | |
| trigger-vercel-production: | |
| needs: [check-automation, check-changesets, publish-production] | |
| if: needs.check-automation.outputs.should-skip != 'true' && needs.publish-production.outputs.published == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Trigger Vercel Production Deployments | |
| id: deploy | |
| run: | | |
| # Parse project IDs from secrets | |
| PROJECT_IDS='${{ secrets.VERCEL_PROJECT_IDS }}' | |
| DEPLOYED_PROJECTS="" | |
| DEPLOYMENT_URLS="" | |
| echo "$PROJECT_IDS" | jq -r 'to_entries[] | "\(.key):\(.value)"' | while IFS=':' read -r PROJECT_NAME PROJECT_ID; do | |
| echo "🚀 Deploying $PROJECT_NAME to production (ID: $PROJECT_ID)..." | |
| # Create production deployment using Vercel API | |
| RESPONSE=$(curl -s -X POST "https://api.vercel.com/v13/deployments" \ | |
| -H "Authorization: Bearer ${{ secrets.VERCEL_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"name\": \"$PROJECT_NAME\", | |
| \"project\": \"$PROJECT_ID\", | |
| \"target\": \"production\", | |
| \"gitSource\": { | |
| \"type\": \"github\", | |
| \"ref\": \"main\", | |
| \"sha\": \"${{ github.sha }}\", | |
| \"repoId\": ${{ github.event.repository.id }} | |
| }, | |
| \"meta\": { | |
| \"githubCommitSha\": \"${{ github.sha }}\", | |
| \"githubCommitAuthorName\": \"${{ github.actor }}\", | |
| \"packageVersion\": \"${{ needs.publish-production.outputs.version }}\", | |
| \"deploymentType\": \"production\", | |
| \"triggeredBy\": \"github-actions\" | |
| } | |
| }") | |
| # Extract deployment URL and ID | |
| DEPLOYMENT_URL=$(echo "$RESPONSE" | jq -r '.url // empty') | |
| DEPLOYMENT_ID=$(echo "$RESPONSE" | jq -r '.id // empty') | |
| ERROR_MESSAGE=$(echo "$RESPONSE" | jq -r '.error.message // empty') | |
| if [ -n "$DEPLOYMENT_URL" ] && [ -n "$DEPLOYMENT_ID" ]; then | |
| echo "✅ Successfully triggered production deployment for $PROJECT_NAME" | |
| echo "📍 Production URL: https://$DEPLOYMENT_URL" | |
| DEPLOYED_PROJECTS="$DEPLOYED_PROJECTS $PROJECT_NAME" | |
| DEPLOYMENT_URLS="$DEPLOYMENT_URLS\\n- **$PROJECT_NAME**: https://$DEPLOYMENT_URL" | |
| else | |
| echo "❌ Failed to deploy $PROJECT_NAME to production" | |
| if [ -n "$ERROR_MESSAGE" ]; then | |
| echo "Error: $ERROR_MESSAGE" | |
| fi | |
| echo "Response: $RESPONSE" | |
| fi | |
| done | |
| # Save results for GitHub release | |
| echo "deployed_projects=$DEPLOYED_PROJECTS" >> $GITHUB_OUTPUT | |
| echo -e "deployment_urls=$DEPLOYMENT_URLS" >> $GITHUB_OUTPUT | |
| - name: Update GitHub Release with Deployment Info | |
| if: steps.deploy.outputs.deployed_projects != '' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const deploymentUrls = `${{ steps.deploy.outputs.deployment_urls }}`; | |
| const tagName = `v${{ needs.publish-production.outputs.version }}`; | |
| // Get the release | |
| let release; | |
| try { | |
| release = await github.rest.repos.getReleaseByTag({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| tag: tagName | |
| }); | |
| } catch (error) { | |
| console.log('Release not found, skipping deployment URL update'); | |
| return; | |
| } | |
| // Update release body with deployment URLs | |
| const updatedBody = release.data.body + | |
| '\n\n## 🚀 Deployment URLs\n' + | |
| deploymentUrls; | |
| await github.rest.repos.updateRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| release_id: release.data.id, | |
| body: updatedBody | |
| }); |