Skip to content

feat: add reusable publish-preview workflow#223

Open
cryptodev-2s wants to merge 9 commits intomainfrom
prepare-preview-builds-action
Open

feat: add reusable publish-preview workflow#223
cryptodev-2s wants to merge 9 commits intomainfrom
prepare-preview-builds-action

Conversation

@cryptodev-2s
Copy link
Contributor

@cryptodev-2s cryptodev-2s commented Feb 26, 2026

Add a reusable workflow_call workflow so consumer repos can replace their copy-pasted publish-preview.yml with a thin wrapper.

New files

  • .github/workflows/publish-preview.yml — Reusable workflow with 4 jobs: fork check, comment reaction, build (no token), publish (with token). Supports both monorepos and polyrepos via is-monorepo input.
  • .github/scripts/generate-preview-build-message.sh — Generates the PR comment (collapsible package list for monorepos, single install command for polyrepos).
  • .github/actions/prepare-preview-builds/ — Composite action for monorepo manifest preparation (from prior commit).

Consumer example

name: Publish a preview build
on:
  issue_comment:
    types: created
jobs:
  publish-preview:
    if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '@metamaskbot publish-preview') }}
    uses: MetaMask/github-tools/.github/workflows/publish-preview.yml@v2
    with:
      is-monorepo: false  # omit for monorepos (default: true)
      environment: default-branch  # optional
    secrets:
      PUBLISH_PREVIEW_NPM_TOKEN: ${{ secrets.PUBLISH_PREVIEW_NPM_TOKEN }}

Extract the preview build preparation logic into a reusable composite
GitHub Action so other repos can use the same logic. The jq filter is
inlined and the source scope is parameterized.
@mcmire
Copy link
Contributor

mcmire commented Feb 26, 2026

@cryptodev-2s What are your thoughts on supporting the entire preview build workflow rather than just the "generate preview builds" step? Recently, we've added support for preview builds to other polyrepos, but currently we have to copy around the workflows. It would be nice to consolidate all of the steps into this repo. And we could make the action support both monorepos and polyrepos. What do you think?

@cryptodev-2s
Copy link
Contributor Author

@cryptodev-2s What are your thoughts on supporting the entire preview build workflow rather than just the "generate preview builds" step? Recently, we've added support for preview builds to other polyrepos, but currently we have to copy around the workflows. It would be nice to consolidate all of the steps into this repo. And we could make the action support both monorepos and polyrepos. What do you think?

Yes make sense, let me do this.

Add a `workflow_call` workflow that handles the full preview build flow
(fork check, comment reaction, build, publish) for both monorepos and
polyrepos. Consumer repos only need a ~13-line thin wrapper with the
`issue_comment` trigger.

Also add a centralized `generate-preview-build-message.sh` script that
auto-detects mono vs polyrepo and produces the appropriate PR comment.
@cryptodev-2s cryptodev-2s changed the title feat: add prepare-preview-builds composite action feat: add reusable publish-preview workflow Feb 26, 2026

- name: Prepare preview builds (monorepo)
if: ${{ inputs.is-monorepo }}
uses: MetaMask/github-tools/.github/actions/prepare-preview-builds@prepare-preview-builds-action
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded branch ref will break after PR merge

High Severity

The uses: reference at @prepare-preview-builds-action points to what appears to be this PR's development branch. Once the PR is merged and the branch is deleted, the monorepo build step will fail for every consumer of this reusable workflow. The publish job correctly resolves the ref dynamically from github.workflow_ref, but the build job hardcodes this ephemeral branch name instead.

Additional Locations (1)

Fix in Cursor Fix in Web

@cryptodev-2s cryptodev-2s requested review from Mrtenz and mcmire March 4, 2026 15:04
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

!./packages/*/node_modules/
!./packages/*/src/
!./packages/*/tests/
!./packages/**/*.test.*
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Monorepo artifact paths hardcode packages/ directory structure

Medium Severity

The monorepo upload glob ./packages/*/ and the sanitization find packages both hardcode the packages/ directory. However, prepare-preview-builds.sh dynamically discovers workspaces via yarn workspaces list, which works for any directory layout. Monorepos with workspaces outside packages/ (e.g., libs/, modules/) would have their manifests prepared but the built output wouldn't be uploaded or security-validated before publishing.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Contributor Author

@cryptodev-2s cryptodev-2s Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All MetaMask monorepos use packages/, this matches the existing core workflow convention. If a repo ever uses a different layout, we can add a workspace-glob input then.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could generate this glob automatically by looping through the workspaces field in package.json. But yes I agree that probably don't need to worry about this for v1.

Copy link
Contributor

@mcmire mcmire Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about turning this into an action and then merging the prepare-preview-builds action into it?

(I have made suggestions below as if the workflow is not being converted, FYI)

- name: Sanitize and validate artifact manifests
run: |
bad=0
if [[ "${{ inputs.is-monorepo }}" == "true" ]]; then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of injecting this should we set it in an environment variable and then read the variable?

# Build a JSON object of { name: version } for all non-root workspace packages.
package_json="$(
yarn workspaces list --no-private --json \
| jq --slurp '[.[] | select(.location != ".")] | .[].location' --raw-output \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to omit .? I thought --no-private should take care of that:

Suggested change
| jq --slurp '[.[] | select(.location != ".")] | .[].location' --raw-output \
| jq --slurp --raw-output '.[].location' \

Comment on lines +73 to +79
cat > preview-build-message.txt <<MSGEOF
Preview builds have been published.${docs_link}

\`\`\`
yarn add ${name}@${version}
\`\`\`
MSGEOF
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we should say:

Suggested change
cat > preview-build-message.txt <<MSGEOF
Preview builds have been published.${docs_link}
\`\`\`
yarn add ${name}@${version}
\`\`\`
MSGEOF
cat <<-MSGEOF > preview-build-message.txt
The following preview build has been published:
\`\`\`
${name}@${version}
\`\`\`
MSGEOF
if [[ -n $docs_link ]]; then
cat <<-MSGEOF >> preview-build-message.txt
${docs_link}
MSGEOF
fi

Comment on lines +55 to +67
cat > preview-build-message.txt <<MSGEOF
Preview builds have been published.${docs_link}

<details>

<summary>Expand for full list of packages and versions.</summary>

\`\`\`
${package_json}
\`\`\`

</details>
MSGEOF
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we should say:

Suggested change
cat > preview-build-message.txt <<MSGEOF
Preview builds have been published.${docs_link}
<details>
<summary>Expand for full list of packages and versions.</summary>
\`\`\`
${package_json}
\`\`\`
</details>
MSGEOF
cat <<-MSGEOF > preview-build-message.txt
Preview builds have been published.
MSGEOF
if [[ -n $docs_link ]]; then
echo -n " ${docs_link}" >> preview-build-message.txt
fi
cat <<-MSGEOF >> preview-build-message.txt
<details>
<summary>Expand for full list of packages and versions.</summary>
\`\`\`
${package_json}
\`\`\`
</details>
MSGEOF

while IFS=$'\t' read -r location name; do
echo "- $name"
prepare-preview-manifest "$location/package.json"
done < <(yarn workspaces list --no-private --json | jq --slurp --raw-output 'map(select(.location != ".")) | map([.location, .name]) | map(@tsv) | .[]')
Copy link
Contributor

@mcmire mcmire Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that the script in core does this, but now that I look at this, do we need to exclude . here either? --no-private should take care of this as well.

Suggested change
done < <(yarn workspaces list --no-private --json | jq --slurp --raw-output 'map(select(.location != ".")) | map([.location, .name]) | map(@tsv) | .[]')
done < <(yarn workspaces list --no-private --json | jq --slurp --raw-output 'map([.location, .name]) | map(@tsv) | .[]')

Comment on lines +45 to +53
# Build a JSON object of { name: version } for all non-root workspace packages.
package_json="$(
yarn workspaces list --no-private --json \
| jq --slurp '[.[] | select(.location != ".")] | .[].location' --raw-output \
| while read -r location; do
jq '{ (.name): .version }' "$location/package.json"
done \
| jq --slurp 'add'
)"
Copy link
Contributor

@mcmire mcmire Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few things:

  1. Instead of building an object here what do you think about listing out the names and versions? The current way that we output the list of versions makes it difficult to update a project's package.json manually.

  2. Do we need to omit .? I thought --no-private should take care of that.

  3. We can use xargs instead of a loop. There is a script in core that does something very similar: https://github.com/MetaMask/core/blob/416032dbbed5c2bf1340572964a09c91d6e06dc7/scripts/list-workspace-versions.sh

Suggested change
# Build a JSON object of { name: version } for all non-root workspace packages.
package_json="$(
yarn workspaces list --no-private --json \
| jq --slurp '[.[] | select(.location != ".")] | .[].location' --raw-output \
| while read -r location; do
jq '{ (.name): .version }' "$location/package.json"
done \
| jq --slurp 'add'
)"
# Build a list of "<name>@<version>" for all non-root workspace packages.
packages="$(
yarn workspaces list --no-private --json | \
jq --raw-output '.location' | \
xargs -I{} cat '{}/package.json' | \
jq --raw-output '"\(.name)@\(.version)"'
)"

- name: Generate preview build message
run: |
args=()
if [[ "${{ inputs.is-monorepo }}" == "true" ]]; then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use environment variables instead of reading inputs directly?

!./packages/*/node_modules/
!./packages/*/src/
!./packages/*/tests/
!./packages/**/*.test.*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could generate this glob automatically by looping through the workspaces field in package.json. But yes I agree that probably don't need to worry about this for v1.

with:
name: preview-build-artifacts

- name: Resolve GitHub tools ref
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this step necessary? I see that for instance check-changelog reads github.action_ref.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants