diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml new file mode 100644 index 0000000..bb98709 --- /dev/null +++ b/.github/workflows/release-publish.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a25ac4..6f1f301 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 @@ -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 <> $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 diff --git a/.yarnrc.yml b/.yarnrc.yml index 91b1101..053bc28 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -3,3 +3,5 @@ compressionLevel: mixed enableGlobalCache: false nodeLinker: node-modules + +npmPublishRegistry: "https://registry.npmjs.org" diff --git a/packages/builder/package.json b/packages/builder/package.json index 5c4667b..938313f 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -11,6 +11,11 @@ ], "author": "OpenZeppelin Community ", "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", diff --git a/packages/cli/package.json b/packages/cli/package.json index 11c8d91..92bac10 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -11,6 +11,11 @@ ], "author": "OpenZeppelin Community ", "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", diff --git a/packages/simulator/package.json b/packages/simulator/package.json index e73b723..80f8627 100644 --- a/packages/simulator/package.json +++ b/packages/simulator/package.json @@ -10,6 +10,11 @@ ], "author": "OpenZeppelin Community ", "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",