From fc2de5b72cc2673f39f16dcedec29286e97a2a8d Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 9 Oct 2025 13:35:40 -0600 Subject: [PATCH 1/4] Add preview builds With these changes, you can post a comment in a PR with the content `@metamaskbot publish-preview`, and it will build the branch and publish it under the `@metamask-previews` NPM scope under a special version. This works very similarly to how it works in `core`. --- .github/workflows/publish-preview.yml | 76 +++++++++++++++++++++++ README.md | 39 ++++++++++++ scripts/generate-preview-build-message.sh | 23 +++++++ scripts/prepare-preview-builds.jq | 9 +++ scripts/prepare-preview-builds.sh | 41 ++++++++++++ 5 files changed, 188 insertions(+) create mode 100644 .github/workflows/publish-preview.yml create mode 100755 scripts/generate-preview-build-message.sh create mode 100644 scripts/prepare-preview-builds.jq create mode 100755 scripts/prepare-preview-builds.sh diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml new file mode 100644 index 00000000..f4a24a0d --- /dev/null +++ b/.github/workflows/publish-preview.yml @@ -0,0 +1,76 @@ +name: Publish a preview build + +on: + issue_comment: + types: created + +jobs: + is-fork-pull-request: + name: Determine whether this issue comment was on a pull request from a fork + if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '@metamaskbot publish-preview') }} + runs-on: ubuntu-latest + outputs: + IS_FORK: ${{ steps.is-fork.outputs.IS_FORK }} + steps: + - uses: actions/checkout@v4 + - name: Determine whether this PR is from a fork + id: is-fork + run: echo "IS_FORK=$(gh pr view --json isCrossRepository --jq '.isCrossRepository' "${PR_NUMBER}" )" >> "$GITHUB_OUTPUT" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.issue.number }} + + react-to-comment: + name: React to the comment + needs: is-fork-pull-request + # This ensures we don't publish on forks. We can't trust forks with this token. + if: ${{ needs.is-fork-pull-request.outputs.IS_FORK == 'false' }} + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: React to the comment + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/${REPO}/issues/comments/${COMMENT_ID}/reactions" \ + -f content='+1' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMMENT_ID: ${{ github.event.comment.id }} + REPO: ${{ github.repository }} + + publish-preview: + name: Publish build preview + needs: react-to-comment + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: actions/checkout@v4 + - name: Check out pull request + run: gh pr checkout "${PR_NUMBER}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.issue.number }} + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v1 + with: + is-high-risk-environment: true + - name: Get commit SHA + id: commit-sha + run: echo "COMMIT_SHA=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" + - run: ./scripts/prepare-preview-builds.sh @metamask-previews ${{ steps.commit-sha.outputs.COMMIT_SHA }} + - run: yarn build + - name: Publish preview build + run: yarn npm publish --tag preview + env: + YARN_NPM_AUTH_TOKEN: ${{ secrets.PUBLISH_PREVIEW_NPM_TOKEN }} + - name: Post build preview in comment + run: ./scripts/generate-preview-build-message.sh | gh pr comment "${PR_NUMBER}" --body-file - + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMMIT_SHA: ${{ steps.commit-sha.outputs.COMMIT_SHA }} + PR_NUMBER: ${{ github.event.issue.number }} diff --git a/README.md b/README.md index b0be71a8..e12f639d 100644 --- a/README.md +++ b/README.md @@ -60,3 +60,42 @@ The project follows the same release process as the other libraries in the MetaM - Wait for the `publish-release` GitHub Action workflow to finish. This should trigger a second job (`publish-npm`), which will wait for a run approval by the [`npm publishers`](https://github.com/orgs/MetaMask/teams/npm-publishers) team. - Approve the `publish-npm` job (or ask somebody on the npm publishers team to approve it for you). - Once the `publish-npm` job has finished, check npm to verify that it has been published. + +## Testing changes in other projects using preview builds + +If you are working on a pull request and want to test changes in another project before you publish them, you can create a _preview build_ and then configure your project to use it. + +### Creating a preview build + +1. Within your pull request, post a comment with the text `@metamaskbot publish-preview`. This starts the `publish-preview` GitHub action, which will create a preview build and publish it to NPM. +2. After a few minutes, the action should complete and you will see a new comment. Note two things: + - The name is scoped to `@metamask-previews` instead of `@metamask`. + - The ID of the last commit in the branch is appended to the version, e.g. `1.2.3-preview-e2df9b4` instead of `1.2.3`. + +### Using a preview build + +To use a preview build within a project, you need to override the resolution logic for your package manager so that the "production" version of that package is replaced with the preview version. Here's how you do that: + +1. Open `package.json` in the project and locate the entry for this package in `dependencies`. +2. Locate the section responsible for resolution overrides (or create it if it doesn't exist). If you're using Yarn, this is `resolutions`; if you're using NPM or any other package manager, this is `overrides`. +3. Add a line to this section that mirrors the dependency entry on the left-hand side and points to the preview version on the right-hand side. Note the exact format of the left-hand side will differ based on which version of Yarn or NPM you are using. For example: + - For Yarn Modern, you will add something like this to `resolutions`: + ``` + "@metamask/ppom-validator@^1.2.3": "npm:@metamask-previews/ppom-validator@1.2.3-preview-abcdefg" + ``` + - For Yarn Classic, you will add something like this to `resolutions`: + ``` + "@metamask/ppom-validator": "npm:@metamask-previews/ppom-validator@1.2.3-preview-abcdefg" + ``` + - For NPM, you will add something like this to `overrides`: + ``` + "@metamask/ppom-validator": "npm:@metamask-previews/ppom-validator@1.2.3-preview-abcdefg" + ``` +4. Run `yarn install`. + +### Updating a preview build + +If you make more changes to your pull request and want to create a new preview build: + +1. Post another `@metamaskbot` comment on the pull request and wait for the response. +2. Update the version of the preview build in your project's `package.json`. Make sure to re-run `yarn install`! diff --git a/scripts/generate-preview-build-message.sh b/scripts/generate-preview-build-message.sh new file mode 100755 index 00000000..ee2b488e --- /dev/null +++ b/scripts/generate-preview-build-message.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +main() { + local package_name + local package_version + + package_name="$(jq -r '.name' < package.json)" + package_version="$(jq -r '.version' < package.json)" + + local preview_build_message="\ +A preview build for this branch has been published. + +You can configure your project to use the preview build with this identifier: + + npm:${package_name}@${package_version} + +[See these instructions](https://github.com/MetaMask/ppom-validator/blob/main/README.md#testing-changes-in-other-projects-using-preview-builds) for more information about preview builds.\ +" + + echo "$preview_build_message" +} + +main "$@" diff --git a/scripts/prepare-preview-builds.jq b/scripts/prepare-preview-builds.jq new file mode 100644 index 00000000..5ba1dd1c --- /dev/null +++ b/scripts/prepare-preview-builds.jq @@ -0,0 +1,9 @@ +# The name is overwritten, causing the package to get published under a +# different NPM scope than non-preview builds. +.name |= sub("@metamask/"; "\($npm_scope)/") | + +# The prerelease version is overwritten, preserving the non-prerelease portion +# of the version. Technically we'd want to bump the non-prerelease portion as +# well if we wanted this to be SemVer-compliant, but it was simpler not to. +# This is just for testing, it doesn't need to strictly follow SemVer. +.version |= split("-")[0] + "-preview-\($short_commit_id)" diff --git a/scripts/prepare-preview-builds.sh b/scripts/prepare-preview-builds.sh new file mode 100755 index 00000000..ebfe2476 --- /dev/null +++ b/scripts/prepare-preview-builds.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# This script prepares a package to be published as a preview build to NPM. + +set -euo pipefail + +prepare-preview-manifest() { + local manifest_file="$1" + local npm_scope="$2" + local short_commit_id="$3" + + # jq does not support in-place modification of files, so a temporary file is + # used to store the result of the operation. The original file is then + # overwritten with the temporary file. + jq --raw-output --arg npm_scope "$npm_scope" --arg short_commit_id "$short_commit_id" --from-file scripts/prepare-preview-builds.jq "$manifest_file" > temp.json + mv temp.json "$manifest_file" +} + +main() { + if [[ $# -eq 0 ]]; then + echo "USAGE: $0 NPM_SCOPE SHORT_GIT_COMMIT_HASH" + exit 1 + fi + + # We don't want to assume that preview builds will be published alongside + # "production" versions. There are security- and aesthetic-based advantages to + # keeping them separate. + local npm_scope="$1" + + # We use the short commit ID as the prerelease version. This ensures each + # preview build is unique and can be linked to a specific commit. + local short_commit_id="$2" + + echo "Preparing manifest..." + prepare-preview-manifest "package.json" "$npm_scope" "$short_commit_id" + + echo "Installing dependencies..." + yarn install --no-immutable +} + +main "$@" From 2702bd44a69a075ede436d628f35c056bfc6036f Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 9 Oct 2025 13:40:04 -0600 Subject: [PATCH 2/4] Fix arg check --- scripts/prepare-preview-builds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/prepare-preview-builds.sh b/scripts/prepare-preview-builds.sh index ebfe2476..3f0f4a93 100755 --- a/scripts/prepare-preview-builds.sh +++ b/scripts/prepare-preview-builds.sh @@ -17,7 +17,7 @@ prepare-preview-manifest() { } main() { - if [[ $# -eq 0 ]]; then + if [[ $# -lt 2 ]]; then echo "USAGE: $0 NPM_SCOPE SHORT_GIT_COMMIT_HASH" exit 1 fi From b4f36f28af415697d55fd12c16ff3aac147de7ca Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 9 Oct 2025 14:02:01 -0600 Subject: [PATCH 3/4] Add 'pull_requests: write' to react-to-comment job --- .github/workflows/publish-preview.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index f4a24a0d..3292231b 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -26,6 +26,8 @@ jobs: # This ensures we don't publish on forks. We can't trust forks with this token. if: ${{ needs.is-fork-pull-request.outputs.IS_FORK == 'false' }} runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - name: Checkout repository uses: actions/checkout@v3 From 288b1ce4b1f60662bf42ef2e8794dbdf1a0814fb Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 9 Oct 2025 15:32:24 -0600 Subject: [PATCH 4/4] Fix workflow --- .github/workflows/publish-preview.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index 3292231b..c4547b48 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -9,6 +9,9 @@ jobs: name: Determine whether this issue comment was on a pull request from a fork if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '@metamaskbot publish-preview') }} runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read outputs: IS_FORK: ${{ steps.is-fork.outputs.IS_FORK }} steps: @@ -17,7 +20,7 @@ jobs: id: is-fork run: echo "IS_FORK=$(gh pr view --json isCrossRepository --jq '.isCrossRepository' "${PR_NUMBER}" )" >> "$GITHUB_OUTPUT" env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.issue.number }} react-to-comment: @@ -27,10 +30,10 @@ jobs: if: ${{ needs.is-fork-pull-request.outputs.IS_FORK == 'false' }} runs-on: ubuntu-latest permissions: + contents: read pull-requests: write steps: - - name: Checkout repository - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: React to the comment run: | gh api \ @@ -40,7 +43,7 @@ jobs: "/repos/${REPO}/issues/comments/${COMMENT_ID}/reactions" \ -f content='+1' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} COMMENT_ID: ${{ github.event.comment.id }} REPO: ${{ github.repository }} @@ -49,13 +52,14 @@ jobs: needs: react-to-comment runs-on: ubuntu-latest permissions: + contents: read pull-requests: write steps: - uses: actions/checkout@v4 - name: Check out pull request run: gh pr checkout "${PR_NUMBER}" env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.issue.number }} - name: Checkout and setup environment uses: MetaMask/action-checkout-and-setup@v1 @@ -73,6 +77,6 @@ jobs: - name: Post build preview in comment run: ./scripts/generate-preview-build-message.sh | gh pr comment "${PR_NUMBER}" --body-file - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} COMMIT_SHA: ${{ steps.commit-sha.outputs.COMMIT_SHA }} PR_NUMBER: ${{ github.event.issue.number }}