Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
name: Publish Release

on:
pull_request:
types: [closed]
branches: [main]
# Escape hatch: re-run a publish that failed mid-flow (e.g. after a fix to
# this workflow). Reads the version from the package.json currently on main.
workflow_dispatch:
inputs:
package:
description: "Package to (re)publish"
required: true
type: choice
options:
- compact-builder
- compact-cli
- compact-simulator

jobs:
publish:
name: Publish merged release
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.pull_request.merged == true &&
contains(github.event.pull_request.labels.*.name, 'release'))
runs-on: ubuntu-24.04
environment: compact-npm-prod # Final approval gate before npm publish

permissions:
contents: write # push the version tag
id-token: write # npm provenance

steps:
- name: Get github app token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
id: gh-app-token
with:
app-id: ${{ vars.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}

# For pull_request runs: checkout the merge commit (deterministic view of
# what was just merged). For manual dispatch: checkout the dispatched ref
# (defaults to main but can be any branch — hotfix, release-prep, etc.).
# The compact-npm-prod environment approval is the security gate, not the
# branch ref.
- name: Check out target ref
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.merge_commit_sha || github.ref }}
token: ${{ steps.gh-app-token.outputs.token }}

- name: Detect released package
id: pkg
env:
EVENT: ${{ github.event_name }}
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
INPUT_PACKAGE: ${{ inputs.package }}
run: |
if [[ "$EVENT" == "workflow_dispatch" ]]; then
PKG="$INPUT_PACKAGE"
case "$PKG" in
compact-builder) DIR="builder" ;;
compact-cli) DIR="cli" ;;
compact-simulator) DIR="simulator" ;;
*)
echo "::error::unknown package: $PKG"
exit 1
;;
esac
else
mapfile -t CHANGED < <(git diff --name-only "$MERGE_SHA^" "$MERGE_SHA" -- 'packages/*/package.json')
COUNT=${#CHANGED[@]}
if [[ "$COUNT" -ne 1 ]]; then
echo "::error::expected exactly one packages/*/package.json change, found $COUNT"
printf '%s\n' "${CHANGED[@]}" >&2
exit 1
fi
DIR=$(echo "${CHANGED[0]}" | awk -F/ '{print $2}')
case "$DIR" in
builder) PKG="compact-builder" ;;
cli) PKG="compact-cli" ;;
simulator) PKG="compact-simulator" ;;
*)
echo "::error::unknown package directory: $DIR"
exit 1
;;
esac
fi
VERSION=$(node -p "require('./packages/$DIR/package.json').version")
echo "dir=$DIR" >> $GITHUB_OUTPUT
echo "name=$PKG" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
{
echo "### Publishing"
echo "- Package: $PKG"
echo "- Version: $VERSION"
echo "- Trigger: $EVENT"
} >> $GITHUB_STEP_SUMMARY

- name: Setup Environment
uses: ./.github/actions/setup

- name: Build package
run: yarn build --filter=@openzeppelin/${{ steps.pkg.outputs.name }}

- name: Create and push tag
env:
TAG: ${{ steps.pkg.outputs.name }}/v${{ steps.pkg.outputs.version }}
run: |
if git ls-remote --tags --exit-code origin "refs/tags/$TAG" >/dev/null; then
echo "tag $TAG already on origin, leaving it in place"
else
git tag "$TAG"
git push origin "$TAG"
fi

- name: Publish to npm
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
yarn config set npmAuthToken "$NPM_TOKEN"
cd packages/${{ steps.pkg.outputs.dir }}
yarn npm publish --access public --provenance
78 changes: 60 additions & 18 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ on:

jobs:
release:
name: Release ${{ inputs.package }}
name: Open release PR for ${{ inputs.package }}
runs-on: ubuntu-24.04
environment: compact-npm-prod # Includes npm token and requires approval
environment: compact-npm-prod # Requires approval before opening the release PR

permissions:
contents: write # Required to push commits and tags
contents: write # create the release branch
pull-requests: write # open the PR + enable auto-merge

steps:
- name: Get github app token
Expand Down Expand Up @@ -74,35 +75,76 @@ jobs:
yarn version ${{ inputs.version_bump }}
NEW_VERSION=$(node -p "require('./package.json').version")
echo "new=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "### Release Summary" >> $GITHUB_STEP_SUMMARY
echo "- Package: ${{ inputs.package }}" >> $GITHUB_STEP_SUMMARY
echo "- New version: $NEW_VERSION" >> $GITHUB_STEP_SUMMARY
echo "- Bump type: ${{ inputs.version_bump }}" >> $GITHUB_STEP_SUMMARY
echo "branch=release/${{ inputs.package }}-v$NEW_VERSION" >> $GITHUB_OUTPUT
{
echo "### Release Summary"
echo "- Package: ${{ inputs.package }}"
echo "- New version: $NEW_VERSION"
echo "- Bump type: ${{ inputs.version_bump }}"
} >> $GITHUB_STEP_SUMMARY

- name: Verify package contents
run: |
cd packages/${{ steps.pkg.outputs.dir }}
yarn pack --dry-run

# Uses GitHub API to create signed commits for verification on protected branches
# Branch protection blocks direct pushes to main, so route the version bump
# through a PR: create branch → signed bot commit → open PR → auto-merge.
- name: Create release branch
env:
GH_TOKEN: ${{ steps.gh-app-token.outputs.token }}
BRANCH: ${{ steps.version.outputs.branch }}
run: |
if gh api "/repos/${{ github.repository }}/git/refs/heads/$BRANCH" >/dev/null 2>&1; then
echo "branch $BRANCH already exists, reusing it"
else
SHA=$(gh api "/repos/${{ github.repository }}/git/refs/heads/${{ github.ref_name }}" -q .object.sha)
gh api --method POST "/repos/${{ github.repository }}/git/refs" \
-f ref="refs/heads/$BRANCH" \
-f sha="$SHA"
fi

- name: Commit version bump
uses: iarekylew00t/verified-bot-commit@934fa64df2191ab067d0c0d73f422239b6933392 # v2.2.1
with:
message: "Release ${{ inputs.package }} v${{ steps.version.outputs.new }}"
message: "release: ${{ inputs.package }} v${{ steps.version.outputs.new }}"
token: ${{ steps.gh-app-token.outputs.token }}
ref: ${{ github.ref_name }}
ref: ${{ steps.version.outputs.branch }}
files: |
packages/${{ steps.pkg.outputs.dir }}/package.json

- name: Create and push tag
- name: Ensure release label exists
env:
GH_TOKEN: ${{ steps.gh-app-token.outputs.token }}
run: |
git tag "${{ inputs.package }}/v${{ steps.version.outputs.new }}"
git push origin "${{ inputs.package }}/v${{ steps.version.outputs.new }}"
gh label create release \
--description "Automated release PR" \
--color ededed \
--force

- name: Publish to npm
- name: Open release PR
id: open-pr
env:
GH_TOKEN: ${{ steps.gh-app-token.outputs.token }}
BRANCH: ${{ steps.version.outputs.branch }}
run: |
yarn config set npmAuthToken "$NPM_TOKEN"
cd packages/${{ steps.pkg.outputs.dir }}
yarn npm publish --access public --provenance
cat > /tmp/pr-body.md <<EOF
Automated release PR for **${{ inputs.package }}** v${{ steps.version.outputs.new }} (${{ inputs.version_bump }} bump).

This PR was opened by the release workflow. Once required checks pass (semgrep, CodeQL, code-owner review), it will auto-merge. Merging will trigger the publish workflow, which tags the release and publishes to npm.
EOF
PR_URL=$(gh pr create \
--base "${{ github.ref_name }}" \
--head "$BRANCH" \
--title "release: ${{ inputs.package }} v${{ steps.version.outputs.new }}" \
--label release \
--body-file /tmp/pr-body.md)
echo "url=$PR_URL" >> $GITHUB_OUTPUT
echo "- PR: $PR_URL" >> $GITHUB_STEP_SUMMARY

# If this step fails with "auto-merge is not allowed", enable it under
# Settings → General → "Allow auto-merge", then re-run the workflow.
- name: Enable auto-merge
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ steps.gh-app-token.outputs.token }}
run: gh pr merge "${{ steps.open-pr.outputs.url }}" --auto --squash
2 changes: 2 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ compressionLevel: mixed
enableGlobalCache: false

nodeLinker: node-modules

npmPublishRegistry: "https://registry.npmjs.org"
5 changes: 5 additions & 0 deletions packages/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
],
"author": "OpenZeppelin Community <maintainers@openzeppelin.org>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/OpenZeppelin/compact-tools.git",
"directory": "packages/builder"
},
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
],
"author": "OpenZeppelin Community <maintainers@openzeppelin.org>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/OpenZeppelin/compact-tools.git",
"directory": "packages/cli"
},
"type": "module",
"exports": {
"./run-builder": "./dist/runBuilder.js",
Expand Down
5 changes: 5 additions & 0 deletions packages/simulator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
],
"author": "OpenZeppelin Community <maintainers@openzeppelin.org>",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/OpenZeppelin/compact-tools.git",
"directory": "packages/simulator"
},
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
Loading