feat: autopublish to npm registry in GH #27
Workflow file for this run
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 to GitHub Packages | |
| on: | |
| # Trigger on pull requests (for testing/validation) - only from same repo | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| # Trigger on pushes to main branch | |
| push: | |
| branches: | |
| - main | |
| # Allow manual triggering | |
| workflow_dispatch: | |
| env: | |
| NODE_VERSION: lts/jod | |
| REGISTRY: npm.pkg.github.com | |
| SCOPE: '@cowprotocol' | |
| permissions: | |
| contents: read | |
| packages: write | |
| issues: write | |
| pull-requests: write | |
| jobs: | |
| publish: | |
| name: Publish to GitHub Packages | |
| runs-on: ubuntu-latest | |
| # Security: Only run on internal PRs, main branch pushes, or external PRs with explicit approval | |
| if: github.event_name == 'push' || (github.event_name == 'pull_request' && (github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'allow-publish'))) | |
| steps: | |
| - name: Security Check | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| echo "🔒 Checking PR security..." | |
| echo "PR from: ${{ github.event.pull_request.head.repo.full_name }}" | |
| echo "Target repo: ${{ github.repository }}" | |
| # Check if it's an internal PR | |
| if [ "${{ github.event.pull_request.head.repo.full_name }}" = "${{ github.repository }}" ]; then | |
| echo "✅ Internal PR - proceeding with publish" | |
| else | |
| # Check for allow-publish label | |
| echo "🔍 Checking for 'allow-publish' label..." | |
| if echo "${{ toJson(github.event.pull_request.labels) }}" | grep -q '"name":"allow-publish"'; then | |
| echo "✅ External PR with 'allow-publish' label - proceeding with publish" | |
| else | |
| echo "❌ External PR without 'allow-publish' label - skipping publish for security" | |
| echo "💡 To enable publishing for this external PR, add the 'allow-publish' label" | |
| exit 1 | |
| fi | |
| fi | |
| else | |
| echo "✅ Push to main branch - proceeding with publish" | |
| fi | |
| - name: Cancel Previous Runs | |
| uses: styfle/[email protected] | |
| with: | |
| access_token: ${{ github.token }} | |
| - name: Remove broken apt repos [Ubuntu] | |
| run: | | |
| for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| scope: ${{ env.SCOPE }} | |
| always-auth: true | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: 10.8.0 | |
| - name: Cache pnpm store | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.pnpm-store | |
| key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Generate code | |
| run: pnpm codegen | |
| - name: Build packages | |
| run: pnpm build | |
| - name: Run tests | |
| run: pnpm test | |
| - name: Run linting | |
| run: pnpm lint | |
| - name: Configure npm for GitHub Packages | |
| run: | | |
| # Override registry for @cowprotocol scope to use GitHub Packages | |
| echo "@cowprotocol:registry=https://${{ env.REGISTRY }}" >> ~/.npmrc | |
| # Always authenticate | |
| echo "always-auth=true" >> ~/.npmrc | |
| # Set auth token | |
| echo "//${{ env.REGISTRY }}/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> ~/.npmrc | |
| # Keep default registry as npmjs.org for other packages | |
| echo "registry=https://registry.npmjs.org/" >> ~/.npmrc | |
| - name: Determine version strategy | |
| id: version | |
| run: | | |
| COMMIT_HASH=$(echo "${{ github.sha }}" | cut -c1-8) | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| # For PRs, use PR number + commit hash for uniqueness | |
| VERSION_SUFFIX="pr-${{ github.event.number }}-$COMMIT_HASH" | |
| echo "version_suffix=$VERSION_SUFFIX" >> $GITHUB_OUTPUT | |
| echo "tag=pr" >> $GITHUB_OUTPUT | |
| echo "is_pr=true" >> $GITHUB_OUTPUT | |
| else | |
| # For main branch, use latest tag | |
| BRANCH_NAME=$(echo "${{ github.ref }}" | sed 's/refs\/heads\///') | |
| VERSION_SUFFIX="${BRANCH_NAME}-$COMMIT_HASH" | |
| echo "version_suffix=$VERSION_SUFFIX" >> $GITHUB_OUTPUT | |
| echo "tag=latest" >> $GITHUB_OUTPUT | |
| echo "is_pr=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Create pre-release versions for PRs | |
| if: steps.version.outputs.is_pr == 'true' | |
| run: | | |
| # Create pre-release versions for all packages | |
| find packages -name "package.json" -exec sh -c ' | |
| for file; do | |
| echo "Creating pre-release version for $file" | |
| # Use npm version to create pre-release version | |
| cd "$(dirname "$file")" | |
| pnpm version prerelease --preid="${{ steps.version.outputs.version_suffix }}" --no-git-tag-version | |
| cd - > /dev/null | |
| done | |
| ' _ {} \; | |
| - name: Publish packages to GitHub Packages | |
| run: | | |
| # Publish all packages and capture versions | |
| echo "Publishing packages..." | |
| pnpm publish -r --filter="./packages/**" --tag ${{ steps.version.outputs.tag }} --no-git-checks --access public > publish_output.txt 2>&1 | |
| # Show the actual publish output for debugging | |
| echo "=== Full publish output ===" | |
| cat publish_output.txt | |
| # Extract published versions and remove duplicates | |
| echo "=== Extracting published versions ===" | |
| grep -o '@cowprotocol/[^@]*@[0-9][^[:space:]]*' publish_output.txt | sort -u > published_versions.txt | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Restore original versions (PR only) | |
| if: steps.version.outputs.is_pr == 'true' | |
| run: | | |
| # Restore original package.json versions | |
| git checkout -- packages/*/package.json | |
| - name: Update PR comment with package info | |
| if: steps.version.outputs.is_pr == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const prVersion = '${{ steps.version.outputs.version_suffix }}'; | |
| const isExternalPR = '${{ github.event.pull_request.head.repo.full_name }}' !== '${{ github.repository }}'; | |
| // Read published versions from file | |
| const fs = require('fs'); | |
| let publishedVersions = []; | |
| try { | |
| const versionsContent = fs.readFileSync('published_versions.txt', 'utf8'); | |
| publishedVersions = versionsContent.trim().split('\n').filter(line => line.trim()); | |
| } catch (error) { | |
| console.log('Could not read published versions file'); | |
| process.exit(1); | |
| } | |
| const now = new Date().toISOString(); | |
| const timestamp = new Date(now).toLocaleString('en-US', { | |
| timeZone: 'UTC', | |
| year: 'numeric', | |
| month: 'short', | |
| day: 'numeric', | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit', | |
| timeZoneName: 'short' | |
| }); | |
| const commentBody = `## 📦 GitHub Packages Published | |
| **Last updated:** ${timestamp} | |
| The following packages have been published to GitHub Packages with pre-release version \`${prVersion}\`: | |
| ${publishedVersions.map(pkg => `- \`${pkg}\``).join('\n')} | |
| ${isExternalPR ? '> **Note:** This is an external PR with the \`allow-publish\` label enabled.' : ''} | |
| --- | |
| ### Installation | |
| These packages require authentication to install from GitHub Packages. First, configure your token: | |
| \`\`\`bash | |
| # Set your GitHub token | |
| # 1. create one at https://github.com/settings/tokens. Make sure you select the option "Generate new token (classic)" | |
| # 2. Check only the "read:packages" scope | |
| # 3. Add the token to the npm config | |
| npm config set //${{ env.REGISTRY }}/:_authToken YOUR_GITHUB_TOKEN | |
| \`\`\` | |
| Then install any of the packages above: | |
| \`\`\`bash | |
| # Yarn | |
| yarn add ${publishedVersions[0]} --registry https://${{ env.REGISTRY }} | |
| # pnpm | |
| pnpm install ${publishedVersions[0]} --registry https://${{ env.REGISTRY }} | |
| # NPM | |
| npm install ${publishedVersions[0]} --registry https://${{ env.REGISTRY }} | |
| \`\`\` | |
| ### View Packages | |
| You can view the published packages at: https://github.com/cowprotocol/cow-sdk/packages`; | |
| // Find existing comment from this workflow | |
| const comments = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number | |
| }); | |
| const existingComment = comments.data.find(comment => | |
| comment.user.type === 'Bot' && | |
| comment.body.includes('📦 GitHub Packages Published') | |
| ); | |
| if (existingComment) { | |
| // Update existing comment | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existingComment.id, | |
| body: commentBody | |
| }); | |
| console.log('Updated existing comment'); | |
| } else { | |
| // Create new comment if none exists | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: commentBody | |
| }); | |
| console.log('Created new comment'); | |
| } |