Skip to content

feat: autopublish to npm registry in GH #26

feat: autopublish to npm registry in GH

feat: autopublish to npm registry in GH #26

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 //npm.pkg.github.com/:_authToken YOUR_GITHUB_TOKEN
\`\`\`
Then install any of the packages above:
\`\`\`bash
# Yarn
yarn add ${publishedVersions[0]} --registry https://npm.pkg.github.com
# pnpm
pnpm install ${publishedVersions[0]} --registry https://npm.pkg.github.com
# NPM
npm install ${publishedVersions[0]} --registry https://npm.pkg.github.com
\`\`\`
### 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');
}