From 06b127c1b3f7e8070dff6578e5504be73c48b69a Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Thu, 26 Feb 2026 01:19:16 +0100 Subject: [PATCH 01/19] feat: add prepare-preview-builds composite action 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. --- .../actions/prepare-preview-builds/action.yml | 56 ++++++++++++++++++ .github/scripts/prepare-preview-builds.sh | 58 +++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 .github/actions/prepare-preview-builds/action.yml create mode 100755 .github/scripts/prepare-preview-builds.sh diff --git a/.github/actions/prepare-preview-builds/action.yml b/.github/actions/prepare-preview-builds/action.yml new file mode 100644 index 00000000..44198505 --- /dev/null +++ b/.github/actions/prepare-preview-builds/action.yml @@ -0,0 +1,56 @@ +name: Prepare Preview Builds + +description: > + Renames workspace packages to a preview NPM scope and adds a prerelease + version tag so they can be published as preview builds. + +inputs: + npm-scope: + description: 'Target NPM scope for preview packages (e.g., @metamask-previews)' + required: true + commit-hash: + description: 'Short commit hash used as the prerelease version tag' + required: true + source-scope: + description: 'Source NPM scope to replace (e.g., @metamask/)' + required: false + default: '@metamask/' + working-directory: + description: 'Path to the monorepo root' + required: false + default: '.' + github-tools-repository: + description: 'The GitHub repository containing the GitHub tools. Defaults to the GitHub tools action repository, and usually does not need to be changed.' + required: false + default: ${{ github.action_repository }} + github-tools-ref: + description: 'The SHA of the action to use. Defaults to the current action ref, and usually does not need to be changed.' + required: false + default: ${{ github.action_ref }} + +runs: + using: composite + steps: + - name: Validate inputs + shell: bash + run: | + if [[ -z "${{ inputs.npm-scope }}" ]]; then + echo "::error::npm-scope input is required" + exit 1 + fi + if [[ -z "${{ inputs.commit-hash }}" ]]; then + echo "::error::commit-hash input is required" + exit 1 + fi + + - name: Checkout GitHub tools repository + uses: actions/checkout@v6 + with: + repository: ${{ inputs.github-tools-repository }} + ref: ${{ inputs.github-tools-ref }} + path: ./.github-tools + + - name: Prepare preview builds + shell: bash + working-directory: ${{ inputs.working-directory }} + run: bash ./.github-tools/.github/scripts/prepare-preview-builds.sh "${{ inputs.npm-scope }}" "${{ inputs.commit-hash }}" "${{ inputs.source-scope }}" diff --git a/.github/scripts/prepare-preview-builds.sh b/.github/scripts/prepare-preview-builds.sh new file mode 100755 index 00000000..c1dc94f3 --- /dev/null +++ b/.github/scripts/prepare-preview-builds.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# This script prepares workspace packages to be published as preview builds. +# It renames packages to a preview NPM scope, sets prerelease versions, and +# reinstalls dependencies so that internal references resolve correctly. +# +# Usage: prepare-preview-builds.sh [source_scope] +# npm_scope - Target NPM scope (e.g., @metamask-previews) +# commit_hash - Short commit hash for the prerelease tag +# source_scope - Source scope to replace (default: @metamask/) + +if [[ $# -lt 2 ]]; then + echo "::error::Usage: prepare-preview-builds.sh [source_scope]" + exit 1 +fi + +npm_scope="$1" +commit_hash="$2" +source_scope="${3:-@metamask/}" + +prepare-preview-manifest() { + local manifest_file="$1" + + # Inline jq filter: rename scope and set prerelease version. + # jq does not support in-place modification, so a temporary file is used. + jq --raw-output \ + --arg npm_scope "$npm_scope" \ + --arg hash "$commit_hash" \ + --arg source_scope "$source_scope" \ + ' + .name |= sub($source_scope; "\($npm_scope)/") | + .version |= (split("-")[0] + "-preview-\($hash)") + ' \ + "$manifest_file" > temp.json + mv temp.json "$manifest_file" +} + +# Add resolutions to the root manifest so that imports under the source scope +# continue to resolve from the local workspace after packages are renamed to +# the preview scope. Without this, yarn resolves them from the npm registry, +# which causes build failures when workspace packages contain changes not yet +# published. +echo "Adding workspace resolutions to root manifest..." +resolutions="$(yarn workspaces list --no-private --json \ + | jq --slurp 'reduce .[] as $pkg ({}; .[$pkg.name] = "portal:./" + $pkg.location)')" +jq --argjson resolutions "$resolutions" '.resolutions = ((.resolutions // {}) + $resolutions)' package.json > temp.json +mv temp.json package.json + +echo "Preparing manifests..." +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) | .[]') + +echo "Installing dependencies..." +yarn install --no-immutable From d25f5a4e3fb341f41c432beb5efad9132bfe6931 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Fri, 27 Feb 2026 00:20:12 +0100 Subject: [PATCH 02/19] feat: add reusable publish-preview workflow 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. --- .../scripts/generate-preview-build-message.sh | 79 ++++++ .github/workflows/publish-preview.yml | 233 ++++++++++++++++++ 2 files changed, 312 insertions(+) create mode 100755 .github/scripts/generate-preview-build-message.sh create mode 100644 .github/workflows/publish-preview.yml diff --git a/.github/scripts/generate-preview-build-message.sh b/.github/scripts/generate-preview-build-message.sh new file mode 100755 index 00000000..eb02bcbe --- /dev/null +++ b/.github/scripts/generate-preview-build-message.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Generates a preview build PR comment message. +# +# Auto-detects monorepo vs polyrepo by checking for workspaces. +# Writes the message to preview-build-message.txt in the current directory. +# +# Usage: generate-preview-build-message.sh [--docs-url ] + +docs_url="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --docs-url) + docs_url="$2" + shift 2 + ;; + *) + echo "::error::Unknown argument: $1" + exit 1 + ;; + esac +done + +# Detect monorepo by checking if yarn workspaces list returns any non-root packages. +is_monorepo=false +if workspace_json="$(yarn workspaces list --no-private --json 2>/dev/null)"; then + non_root_count="$(echo "$workspace_json" | jq --slurp '[.[] | select(.location != ".")] | length')" + if [[ "$non_root_count" -gt 0 ]]; then + is_monorepo=true + fi +fi + +docs_link="" +if [[ -n "$docs_url" ]]; then + docs_link=" [See these instructions](${docs_url}) for more information about preview builds." +fi + +if [[ "$is_monorepo" == "true" ]]; then + # Build a JSON object of { name: version } for all non-root workspace packages. + package_json="$( + echo "$workspace_json" \ + | jq --slurp '[.[] | select(.location != ".")] | .[].location' --raw-output \ + | while read -r location; do + jq '{ (.name): .version }' "$location/package.json" + done \ + | jq --slurp 'add' + )" + + cat > preview-build-message.txt < + +Expand for full list of packages and versions. + +\`\`\` +${package_json} +\`\`\` + + +MSGEOF +else + # Polyrepo: single package in root. + name="$(jq -r '.name' package.json)" + version="$(jq -r '.version' package.json)" + + cat > preview-build-message.txt <> "$GITHUB_OUTPUT" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.issue.number }} + + react-to-comment: + name: React to comment + needs: is-fork-pull-request + if: ${{ needs.is-fork-pull-request.outputs.IS_FORK == 'false' }} + runs-on: ubuntu-latest + steps: + - name: Add reaction to trigger comment + run: | + gh api \ + --method POST \ + "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}/reactions" \ + -f content='+1' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMMENT_ID: ${{ github.event.comment.id }} + + build-preview: + name: Build preview + needs: react-to-comment + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - 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@v2 + with: + is-high-risk-environment: true + + - name: Get commit SHA + id: commit-sha + run: echo "COMMIT_SHA=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" + + - name: Prepare preview builds (monorepo) + if: ${{ inputs.is-monorepo }} + uses: MetaMask/github-tools/.github/actions/prepare-preview-builds@v1 + with: + npm-scope: ${{ inputs.npm-scope }} + commit-hash: ${{ steps.commit-sha.outputs.COMMIT_SHA }} + source-scope: ${{ inputs.source-scope }} + + - name: Prepare preview builds (polyrepo) + if: ${{ !inputs.is-monorepo }} + run: | + jq \ + --arg npm_scope "${{ inputs.npm-scope }}" \ + --arg hash "${{ steps.commit-sha.outputs.COMMIT_SHA }}" \ + --arg source_scope "${{ inputs.source-scope }}" \ + ' + .name |= sub($source_scope; "\($npm_scope)/") | + .version |= (split("-")[0] + "-preview-\($hash)") + ' \ + package.json > temp.json + mv temp.json package.json + + - name: Build + run: ${{ inputs.build-command }} + + - name: Upload build artifacts (monorepo) + if: ${{ inputs.is-monorepo }} + uses: actions/upload-artifact@v6 + with: + name: preview-build-artifacts + include-hidden-files: true + retention-days: ${{ inputs.artifact-retention-days }} + path: | + ./yarn.lock + ./package.json + ./packages/*/ + !./packages/*/node_modules/ + !./packages/*/src/ + !./packages/*/tests/ + !./packages/**/*.test.* + + - name: Upload build artifacts (polyrepo) + if: ${{ !inputs.is-monorepo }} + uses: actions/upload-artifact@v6 + with: + name: preview-build-artifacts + include-hidden-files: true + retention-days: ${{ inputs.artifact-retention-days }} + path: | + . + !./node_modules/ + !./.git/ + + publish-preview: + name: Publish preview + needs: build-preview + permissions: + pull-requests: write + environment: ${{ inputs.environment }} + runs-on: ubuntu-latest + steps: + - name: Checkout and setup environment + uses: MetaMask/action-checkout-and-setup@v2 + with: + is-high-risk-environment: true + + - name: Restore build artifacts + uses: actions/download-artifact@v7 + with: + name: preview-build-artifacts + + - name: Checkout GitHub tools repository + uses: actions/checkout@v5 + with: + repository: MetaMask/github-tools + ref: ${{ github.workflow_sha }} + path: ./.github-tools + + # The artifact package.json files come from the PR branch. + # A malicious PR could inject lifecycle scripts (prepack/postpack) that + # execute during `yarn npm publish` with the NPM token in the environment + # (enableScripts: false does NOT prevent pack/publish lifecycle scripts). + # It could also override publishConfig.registry to exfiltrate the token. + - name: Validate artifact manifests + run: | + bad=0 + if [[ "${{ inputs.is-monorepo }}" == "true" ]]; then + mapfile -t manifests < <(find packages -name package.json -not -path '*/node_modules/*') + else + manifests=(package.json) + fi + if [[ ${#manifests[@]} -eq 0 ]]; then + echo "::error::No package.json files found to validate" + exit 1 + fi + for f in "${manifests[@]}"; do + if jq -e '.scripts // {} | keys[] | select(test("^(pre|post)(pack|publish)"))' "$f" > /dev/null 2>&1; then + echo "::error::Forbidden lifecycle script in $f" + bad=1 + fi + reg=$(jq -r '.publishConfig.registry // ""' "$f") + if [[ -n "$reg" && "$reg" != "https://registry.npmjs.org/" ]]; then + echo "::error::Unexpected registry in $f: $reg" + bad=1 + fi + done + exit "$bad" + + - name: Reconcile workspace state + run: yarn install --no-immutable + + - name: Publish preview builds (monorepo) + if: ${{ inputs.is-monorepo }} + run: yarn workspaces foreach --no-private --all exec yarn npm publish --tag preview + env: + YARN_NPM_AUTH_TOKEN: ${{ secrets.PUBLISH_PREVIEW_NPM_TOKEN }} + + - name: Publish preview builds (polyrepo) + if: ${{ !inputs.is-monorepo }} + run: yarn npm publish --tag preview + env: + YARN_NPM_AUTH_TOKEN: ${{ secrets.PUBLISH_PREVIEW_NPM_TOKEN }} + + - name: Generate preview build message + run: | + docs_args=() + if [[ -n "${{ inputs.docs-url }}" ]]; then + docs_args=(--docs-url "${{ inputs.docs-url }}") + fi + bash ./.github-tools/.github/scripts/generate-preview-build-message.sh "${docs_args[@]}" + + - name: Post build preview in comment + run: gh pr comment "${PR_NUMBER}" --body-file preview-build-message.txt + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.issue.number }} From 5a9d9bc69b2132c63769664d1ea6f0ed503df551 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Fri, 27 Feb 2026 00:27:38 +0100 Subject: [PATCH 03/19] fix: use absolute script path in prepare-preview-builds action --- .github/actions/prepare-preview-builds/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/prepare-preview-builds/action.yml b/.github/actions/prepare-preview-builds/action.yml index 44198505..58c3bdfa 100644 --- a/.github/actions/prepare-preview-builds/action.yml +++ b/.github/actions/prepare-preview-builds/action.yml @@ -53,4 +53,4 @@ runs: - name: Prepare preview builds shell: bash working-directory: ${{ inputs.working-directory }} - run: bash ./.github-tools/.github/scripts/prepare-preview-builds.sh "${{ inputs.npm-scope }}" "${{ inputs.commit-hash }}" "${{ inputs.source-scope }}" + run: bash "$GITHUB_WORKSPACE/.github-tools/.github/scripts/prepare-preview-builds.sh" "${{ inputs.npm-scope }}" "${{ inputs.commit-hash }}" "${{ inputs.source-scope }}" From 0f2aa18dfd15ec75577834d96528d1c5576cc7f0 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Fri, 27 Feb 2026 00:45:28 +0100 Subject: [PATCH 04/19] test: point prepare-preview-builds action at branch ref --- .github/workflows/publish-preview.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index 9c34b087..3c1a8a43 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -97,7 +97,7 @@ jobs: - name: Prepare preview builds (monorepo) if: ${{ inputs.is-monorepo }} - uses: MetaMask/github-tools/.github/actions/prepare-preview-builds@v1 + uses: MetaMask/github-tools/.github/actions/prepare-preview-builds@prepare-preview-builds-action with: npm-scope: ${{ inputs.npm-scope }} commit-hash: ${{ steps.commit-sha.outputs.COMMIT_SHA }} From 08df742bc805ea2e433e2e610316f24555edb29f Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Fri, 27 Feb 2026 00:55:23 +0100 Subject: [PATCH 05/19] fix: resolve github-tools ref from github.workflow_ref --- .github/workflows/publish-preview.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index 3c1a8a43..230502ef 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -166,11 +166,20 @@ jobs: with: name: preview-build-artifacts + - name: Resolve GitHub tools ref + id: github-tools-ref + run: | + # github.workflow_ref contains e.g. "Owner/repo/.github/workflows/file.yml@refs/heads/branch" + ref="${WORKFLOW_REF##*@}" + echo "ref=$ref" >> "$GITHUB_OUTPUT" + env: + WORKFLOW_REF: ${{ github.workflow_ref }} + - name: Checkout GitHub tools repository uses: actions/checkout@v5 with: repository: MetaMask/github-tools - ref: ${{ github.workflow_sha }} + ref: ${{ steps.github-tools-ref.outputs.ref }} path: ./.github-tools # The artifact package.json files come from the PR branch. From c65f89b2797b50b951dee87d38bb9164bab33cd2 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Wed, 4 Mar 2026 15:31:31 +0000 Subject: [PATCH 06/19] fix: run yarn install after polyrepo package rename --- .github/workflows/publish-preview.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index 230502ef..b0649176 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -116,6 +116,7 @@ jobs: ' \ package.json > temp.json mv temp.json package.json + yarn install --no-immutable - name: Build run: ${{ inputs.build-command }} From fa010bacdd1bb7b9f629a22ac8f42eb435ca235f Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Wed, 4 Mar 2026 19:27:22 +0000 Subject: [PATCH 07/19] fix: strip lifecycle scripts from artifacts instead of failing --- .github/workflows/publish-preview.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index b0649176..ac0cf87f 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -188,7 +188,9 @@ jobs: # execute during `yarn npm publish` with the NPM token in the environment # (enableScripts: false does NOT prevent pack/publish lifecycle scripts). # It could also override publishConfig.registry to exfiltrate the token. - - name: Validate artifact manifests + # We strip dangerous lifecycle scripts (they already ran during build) + # and block unexpected registries outright. + - name: Sanitize and validate artifact manifests run: | bad=0 if [[ "${{ inputs.is-monorepo }}" == "true" ]]; then @@ -201,10 +203,13 @@ jobs: exit 1 fi for f in "${manifests[@]}"; do + # Strip lifecycle scripts that run during pack/publish if jq -e '.scripts // {} | keys[] | select(test("^(pre|post)(pack|publish)"))' "$f" > /dev/null 2>&1; then - echo "::error::Forbidden lifecycle script in $f" - bad=1 + echo "Stripping pack/publish lifecycle scripts from $f" + jq 'if .scripts then .scripts |= with_entries(select(.key | test("^(pre|post)(pack|publish)") | not)) else . end' "$f" > "${f}.tmp" + mv "${f}.tmp" "$f" fi + # Block unexpected registries reg=$(jq -r '.publishConfig.registry // ""' "$f") if [[ -n "$reg" && "$reg" != "https://registry.npmjs.org/" ]]; then echo "::error::Unexpected registry in $f: $reg" From fad8410f31fc3688e09f736e8f5c15bfeac1d8cc Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Thu, 5 Mar 2026 15:30:18 +0000 Subject: [PATCH 08/19] fix: also strip prepare lifecycle script from artifacts --- .github/workflows/publish-preview.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index ac0cf87f..82dbea7c 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -204,9 +204,9 @@ jobs: fi for f in "${manifests[@]}"; do # Strip lifecycle scripts that run during pack/publish - if jq -e '.scripts // {} | keys[] | select(test("^(pre|post)(pack|publish)"))' "$f" > /dev/null 2>&1; then - echo "Stripping pack/publish lifecycle scripts from $f" - jq 'if .scripts then .scripts |= with_entries(select(.key | test("^(pre|post)(pack|publish)") | not)) else . end' "$f" > "${f}.tmp" + if jq -e '.scripts // {} | keys[] | select(test("^(pre|post)?(pack|publish|prepare)$"))' "$f" > /dev/null 2>&1; then + echo "Stripping lifecycle scripts from $f" + jq 'if .scripts then .scripts |= with_entries(select(.key | test("^(pre|post)?(pack|publish|prepare)$") | not)) else . end' "$f" > "${f}.tmp" mv "${f}.tmp" "$f" fi # Block unexpected registries From 5fa209e2c7ca2b6e8904a51a49f5a05765d5d72f Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Thu, 5 Mar 2026 15:36:35 +0000 Subject: [PATCH 09/19] refactor: pass monorepo/polyrepo flag explicitly to message script --- .../scripts/generate-preview-build-message.sh | 25 +++++++++++-------- .github/workflows/publish-preview.yml | 11 +++++--- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/.github/scripts/generate-preview-build-message.sh b/.github/scripts/generate-preview-build-message.sh index eb02bcbe..ebeeb6c4 100755 --- a/.github/scripts/generate-preview-build-message.sh +++ b/.github/scripts/generate-preview-build-message.sh @@ -3,16 +3,23 @@ set -euo pipefail # Generates a preview build PR comment message. -# -# Auto-detects monorepo vs polyrepo by checking for workspaces. # Writes the message to preview-build-message.txt in the current directory. # -# Usage: generate-preview-build-message.sh [--docs-url ] +# Usage: generate-preview-build-message.sh --monorepo|--polyrepo [--docs-url ] docs_url="" +is_monorepo="" while [[ $# -gt 0 ]]; do case "$1" in + --monorepo) + is_monorepo=true + shift + ;; + --polyrepo) + is_monorepo=false + shift + ;; --docs-url) docs_url="$2" shift 2 @@ -24,13 +31,9 @@ while [[ $# -gt 0 ]]; do esac done -# Detect monorepo by checking if yarn workspaces list returns any non-root packages. -is_monorepo=false -if workspace_json="$(yarn workspaces list --no-private --json 2>/dev/null)"; then - non_root_count="$(echo "$workspace_json" | jq --slurp '[.[] | select(.location != ".")] | length')" - if [[ "$non_root_count" -gt 0 ]]; then - is_monorepo=true - fi +if [[ -z "$is_monorepo" ]]; then + echo "::error::Must specify --monorepo or --polyrepo" + exit 1 fi docs_link="" @@ -41,7 +44,7 @@ fi if [[ "$is_monorepo" == "true" ]]; then # Build a JSON object of { name: version } for all non-root workspace packages. package_json="$( - echo "$workspace_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" diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index 82dbea7c..a48d3ba1 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -235,11 +235,16 @@ jobs: - name: Generate preview build message run: | - docs_args=() + args=() + if [[ "${{ inputs.is-monorepo }}" == "true" ]]; then + args+=(--monorepo) + else + args+=(--polyrepo) + fi if [[ -n "${{ inputs.docs-url }}" ]]; then - docs_args=(--docs-url "${{ inputs.docs-url }}") + args+=(--docs-url "${{ inputs.docs-url }}") fi - bash ./.github-tools/.github/scripts/generate-preview-build-message.sh "${docs_args[@]}" + bash ./.github-tools/.github/scripts/generate-preview-build-message.sh "${args[@]}" - name: Post build preview in comment run: gh pr comment "${PR_NUMBER}" --body-file preview-build-message.txt From 788fc305d5bdb7b83f980ec06eed3763fb94735b Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Mon, 9 Mar 2026 16:18:16 +0000 Subject: [PATCH 10/19] refactor: address review feedback --- .../scripts/generate-preview-build-message.sh | 38 +++++++++++-------- .github/scripts/prepare-preview-builds.sh | 2 +- .github/workflows/publish-preview.yml | 23 +++++++---- 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/.github/scripts/generate-preview-build-message.sh b/.github/scripts/generate-preview-build-message.sh index ebeeb6c4..7e345019 100755 --- a/.github/scripts/generate-preview-build-message.sh +++ b/.github/scripts/generate-preview-build-message.sh @@ -38,29 +38,31 @@ fi docs_link="" if [[ -n "$docs_url" ]]; then - docs_link=" [See these instructions](${docs_url}) for more information about preview builds." + docs_link="[See these instructions](${docs_url}) for more information about preview builds." fi if [[ "$is_monorepo" == "true" ]]; then - # Build a JSON object of { name: version } for all non-root workspace packages. - package_json="$( + # Build a list of "name@version" for all workspace packages. + packages="$( 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' + | jq --raw-output '.location' \ + | xargs -I{} cat '{}/package.json' \ + | jq --raw-output '"\(.name)@\(.version)"' )" - cat > preview-build-message.txt < 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
- Expand for full list of packages and versions. \`\`\` -${package_json} +${packages} \`\`\`
@@ -70,13 +72,19 @@ else name="$(jq -r '.name' package.json)" version="$(jq -r '.version' package.json)" - cat > preview-build-message.txt < preview-build-message.txt +The following preview build has been published: \`\`\` -yarn add ${name}@${version} +${name}@${version} \`\`\` MSGEOF + if [[ -n "$docs_link" ]]; then + cat <<-MSGEOF >> preview-build-message.txt + +${docs_link} +MSGEOF + fi fi echo "Preview build message written to preview-build-message.txt" diff --git a/.github/scripts/prepare-preview-builds.sh b/.github/scripts/prepare-preview-builds.sh index c1dc94f3..6b54623c 100755 --- a/.github/scripts/prepare-preview-builds.sh +++ b/.github/scripts/prepare-preview-builds.sh @@ -52,7 +52,7 @@ echo "Preparing manifests..." 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) | .[]') +done < <(yarn workspaces list --no-private --json | jq --slurp --raw-output 'map([.location, .name]) | map(@tsv) | .[]') echo "Installing dependencies..." yarn install --no-immutable diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index a48d3ba1..5ebd8b6a 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -107,9 +107,9 @@ jobs: if: ${{ !inputs.is-monorepo }} run: | jq \ - --arg npm_scope "${{ inputs.npm-scope }}" \ - --arg hash "${{ steps.commit-sha.outputs.COMMIT_SHA }}" \ - --arg source_scope "${{ inputs.source-scope }}" \ + --arg npm_scope "$NPM_SCOPE" \ + --arg hash "$COMMIT_SHA" \ + --arg source_scope "$SOURCE_SCOPE" \ ' .name |= sub($source_scope; "\($npm_scope)/") | .version |= (split("-")[0] + "-preview-\($hash)") @@ -117,6 +117,10 @@ jobs: package.json > temp.json mv temp.json package.json yarn install --no-immutable + env: + NPM_SCOPE: ${{ inputs.npm-scope }} + COMMIT_SHA: ${{ steps.commit-sha.outputs.COMMIT_SHA }} + SOURCE_SCOPE: ${{ inputs.source-scope }} - name: Build run: ${{ inputs.build-command }} @@ -191,9 +195,11 @@ jobs: # We strip dangerous lifecycle scripts (they already ran during build) # and block unexpected registries outright. - name: Sanitize and validate artifact manifests + env: + IS_MONOREPO: ${{ inputs.is-monorepo }} run: | bad=0 - if [[ "${{ inputs.is-monorepo }}" == "true" ]]; then + if [[ "$IS_MONOREPO" == "true" ]]; then mapfile -t manifests < <(find packages -name package.json -not -path '*/node_modules/*') else manifests=(package.json) @@ -234,15 +240,18 @@ jobs: YARN_NPM_AUTH_TOKEN: ${{ secrets.PUBLISH_PREVIEW_NPM_TOKEN }} - name: Generate preview build message + env: + IS_MONOREPO: ${{ inputs.is-monorepo }} + DOCS_URL: ${{ inputs.docs-url }} run: | args=() - if [[ "${{ inputs.is-monorepo }}" == "true" ]]; then + if [[ "$IS_MONOREPO" == "true" ]]; then args+=(--monorepo) else args+=(--polyrepo) fi - if [[ -n "${{ inputs.docs-url }}" ]]; then - args+=(--docs-url "${{ inputs.docs-url }}") + if [[ -n "$DOCS_URL" ]]; then + args+=(--docs-url "$DOCS_URL") fi bash ./.github-tools/.github/scripts/generate-preview-build-message.sh "${args[@]}" From ea06dbd3c90e976f1a28c74a9fb9ba51429e230b Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Mon, 9 Mar 2026 16:25:51 +0000 Subject: [PATCH 11/19] feat: add dry-run input to skip NPM publish --- .github/workflows/publish-preview.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index 5ebd8b6a..8934f5ba 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -38,6 +38,11 @@ on: type: string required: false default: '' + dry-run: + description: 'Skip actual NPM publish (for testing)' + type: boolean + required: false + default: false secrets: PUBLISH_PREVIEW_NPM_TOKEN: required: true @@ -228,17 +233,21 @@ jobs: run: yarn install --no-immutable - name: Publish preview builds (monorepo) - if: ${{ inputs.is-monorepo }} + if: ${{ inputs.is-monorepo && !inputs.dry-run }} run: yarn workspaces foreach --no-private --all exec yarn npm publish --tag preview env: YARN_NPM_AUTH_TOKEN: ${{ secrets.PUBLISH_PREVIEW_NPM_TOKEN }} - name: Publish preview builds (polyrepo) - if: ${{ !inputs.is-monorepo }} + if: ${{ !inputs.is-monorepo && !inputs.dry-run }} run: yarn npm publish --tag preview env: YARN_NPM_AUTH_TOKEN: ${{ secrets.PUBLISH_PREVIEW_NPM_TOKEN }} + - name: Dry run notice + if: ${{ inputs.dry-run }} + run: echo "Dry run — skipping publish" + - name: Generate preview build message env: IS_MONOREPO: ${{ inputs.is-monorepo }} From 03bc0eeb33b5372858c62fbcc33aa01f145b5d73 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Mon, 9 Mar 2026 16:44:34 +0000 Subject: [PATCH 12/19] fix: monorepo docs link appearing on wrong line --- .github/scripts/generate-preview-build-message.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/scripts/generate-preview-build-message.sh b/.github/scripts/generate-preview-build-message.sh index 7e345019..2156b517 100755 --- a/.github/scripts/generate-preview-build-message.sh +++ b/.github/scripts/generate-preview-build-message.sh @@ -50,9 +50,7 @@ if [[ "$is_monorepo" == "true" ]]; then | jq --raw-output '"\(.name)@\(.version)"' )" - cat <<-MSGEOF > preview-build-message.txt -Preview builds have been published. -MSGEOF + echo -n "Preview builds have been published." > preview-build-message.txt if [[ -n "$docs_link" ]]; then echo -n " ${docs_link}" >> preview-build-message.txt fi From d4696d12764525b7d16f49121e2a5b8feec667e3 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Mon, 9 Mar 2026 16:53:25 +0000 Subject: [PATCH 13/19] fix: inline message generation, remove github-tools checkout --- .github/workflows/publish-preview.yml | 61 +++++++++++++++++---------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index 8934f5ba..865eb61a 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -176,22 +176,6 @@ jobs: with: name: preview-build-artifacts - - name: Resolve GitHub tools ref - id: github-tools-ref - run: | - # github.workflow_ref contains e.g. "Owner/repo/.github/workflows/file.yml@refs/heads/branch" - ref="${WORKFLOW_REF##*@}" - echo "ref=$ref" >> "$GITHUB_OUTPUT" - env: - WORKFLOW_REF: ${{ github.workflow_ref }} - - - name: Checkout GitHub tools repository - uses: actions/checkout@v5 - with: - repository: MetaMask/github-tools - ref: ${{ steps.github-tools-ref.outputs.ref }} - path: ./.github-tools - # The artifact package.json files come from the PR branch. # A malicious PR could inject lifecycle scripts (prepack/postpack) that # execute during `yarn npm publish` with the NPM token in the environment @@ -253,16 +237,47 @@ jobs: IS_MONOREPO: ${{ inputs.is-monorepo }} DOCS_URL: ${{ inputs.docs-url }} run: | - args=() + docs_link="" + if [[ -n "$DOCS_URL" ]]; then + docs_link="[See these instructions](${DOCS_URL}) for more information about preview builds." + fi + if [[ "$IS_MONOREPO" == "true" ]]; then - args+=(--monorepo) + packages="$( + yarn workspaces list --no-private --json \ + | jq --raw-output '.location' \ + | xargs -I{} cat '{}/package.json' \ + | jq --raw-output '"\(.name)@\(.version)"' + )" + echo -n "Preview builds have been published." > preview-build-message.txt + if [[ -n "$docs_link" ]]; then + echo -n " ${docs_link}" >> preview-build-message.txt + fi + cat <<-MSGEOF >> preview-build-message.txt + +
+ Expand for full list of packages and versions. + + \`\`\` + ${packages} + \`\`\` + +
+ MSGEOF else - args+=(--polyrepo) - fi - if [[ -n "$DOCS_URL" ]]; then - args+=(--docs-url "$DOCS_URL") + name="$(jq -r '.name' package.json)" + version="$(jq -r '.version' package.json)" + cat <<-MSGEOF > preview-build-message.txt + The following preview build has been published: + + \`\`\` + ${name}@${version} + \`\`\` + MSGEOF + if [[ -n "$docs_link" ]]; then + printf '\n%s\n' "$docs_link" >> preview-build-message.txt + fi fi - bash ./.github-tools/.github/scripts/generate-preview-build-message.sh "${args[@]}" - name: Post build preview in comment run: gh pr comment "${PR_NUMBER}" --body-file preview-build-message.txt From 9db8b7efc0c320b364e82b7f1dacc8821d63b622 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Mon, 9 Mar 2026 16:54:54 +0000 Subject: [PATCH 14/19] chore: remove unused generate-preview-build-message.sh --- .../scripts/generate-preview-build-message.sh | 88 ------------------- 1 file changed, 88 deletions(-) delete mode 100755 .github/scripts/generate-preview-build-message.sh diff --git a/.github/scripts/generate-preview-build-message.sh b/.github/scripts/generate-preview-build-message.sh deleted file mode 100755 index 2156b517..00000000 --- a/.github/scripts/generate-preview-build-message.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Generates a preview build PR comment message. -# Writes the message to preview-build-message.txt in the current directory. -# -# Usage: generate-preview-build-message.sh --monorepo|--polyrepo [--docs-url ] - -docs_url="" -is_monorepo="" - -while [[ $# -gt 0 ]]; do - case "$1" in - --monorepo) - is_monorepo=true - shift - ;; - --polyrepo) - is_monorepo=false - shift - ;; - --docs-url) - docs_url="$2" - shift 2 - ;; - *) - echo "::error::Unknown argument: $1" - exit 1 - ;; - esac -done - -if [[ -z "$is_monorepo" ]]; then - echo "::error::Must specify --monorepo or --polyrepo" - exit 1 -fi - -docs_link="" -if [[ -n "$docs_url" ]]; then - docs_link="[See these instructions](${docs_url}) for more information about preview builds." -fi - -if [[ "$is_monorepo" == "true" ]]; then - # Build a list of "name@version" for all workspace packages. - packages="$( - yarn workspaces list --no-private --json \ - | jq --raw-output '.location' \ - | xargs -I{} cat '{}/package.json' \ - | jq --raw-output '"\(.name)@\(.version)"' - )" - - echo -n "Preview builds have been published." > preview-build-message.txt - if [[ -n "$docs_link" ]]; then - echo -n " ${docs_link}" >> preview-build-message.txt - fi - cat <<-MSGEOF >> preview-build-message.txt - -
-Expand for full list of packages and versions. - -\`\`\` -${packages} -\`\`\` - -
-MSGEOF -else - # Polyrepo: single package in root. - name="$(jq -r '.name' package.json)" - version="$(jq -r '.version' package.json)" - - 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 -fi - -echo "Preview build message written to preview-build-message.txt" From 7f99a4eedb10dd0f2fdea567557a75f538b5d36d Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Mon, 16 Mar 2026 16:10:32 +0100 Subject: [PATCH 15/19] refactor: inline prepare-preview-builds into workflow --- .../actions/prepare-preview-builds/action.yml | 56 ------------------ .github/scripts/prepare-preview-builds.sh | 58 ------------------ .github/workflows/publish-preview.yml | 59 ++++++++++++------- 3 files changed, 37 insertions(+), 136 deletions(-) delete mode 100644 .github/actions/prepare-preview-builds/action.yml delete mode 100755 .github/scripts/prepare-preview-builds.sh diff --git a/.github/actions/prepare-preview-builds/action.yml b/.github/actions/prepare-preview-builds/action.yml deleted file mode 100644 index 58c3bdfa..00000000 --- a/.github/actions/prepare-preview-builds/action.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Prepare Preview Builds - -description: > - Renames workspace packages to a preview NPM scope and adds a prerelease - version tag so they can be published as preview builds. - -inputs: - npm-scope: - description: 'Target NPM scope for preview packages (e.g., @metamask-previews)' - required: true - commit-hash: - description: 'Short commit hash used as the prerelease version tag' - required: true - source-scope: - description: 'Source NPM scope to replace (e.g., @metamask/)' - required: false - default: '@metamask/' - working-directory: - description: 'Path to the monorepo root' - required: false - default: '.' - github-tools-repository: - description: 'The GitHub repository containing the GitHub tools. Defaults to the GitHub tools action repository, and usually does not need to be changed.' - required: false - default: ${{ github.action_repository }} - github-tools-ref: - description: 'The SHA of the action to use. Defaults to the current action ref, and usually does not need to be changed.' - required: false - default: ${{ github.action_ref }} - -runs: - using: composite - steps: - - name: Validate inputs - shell: bash - run: | - if [[ -z "${{ inputs.npm-scope }}" ]]; then - echo "::error::npm-scope input is required" - exit 1 - fi - if [[ -z "${{ inputs.commit-hash }}" ]]; then - echo "::error::commit-hash input is required" - exit 1 - fi - - - name: Checkout GitHub tools repository - uses: actions/checkout@v6 - with: - repository: ${{ inputs.github-tools-repository }} - ref: ${{ inputs.github-tools-ref }} - path: ./.github-tools - - - name: Prepare preview builds - shell: bash - working-directory: ${{ inputs.working-directory }} - run: bash "$GITHUB_WORKSPACE/.github-tools/.github/scripts/prepare-preview-builds.sh" "${{ inputs.npm-scope }}" "${{ inputs.commit-hash }}" "${{ inputs.source-scope }}" diff --git a/.github/scripts/prepare-preview-builds.sh b/.github/scripts/prepare-preview-builds.sh deleted file mode 100755 index 6b54623c..00000000 --- a/.github/scripts/prepare-preview-builds.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# This script prepares workspace packages to be published as preview builds. -# It renames packages to a preview NPM scope, sets prerelease versions, and -# reinstalls dependencies so that internal references resolve correctly. -# -# Usage: prepare-preview-builds.sh [source_scope] -# npm_scope - Target NPM scope (e.g., @metamask-previews) -# commit_hash - Short commit hash for the prerelease tag -# source_scope - Source scope to replace (default: @metamask/) - -if [[ $# -lt 2 ]]; then - echo "::error::Usage: prepare-preview-builds.sh [source_scope]" - exit 1 -fi - -npm_scope="$1" -commit_hash="$2" -source_scope="${3:-@metamask/}" - -prepare-preview-manifest() { - local manifest_file="$1" - - # Inline jq filter: rename scope and set prerelease version. - # jq does not support in-place modification, so a temporary file is used. - jq --raw-output \ - --arg npm_scope "$npm_scope" \ - --arg hash "$commit_hash" \ - --arg source_scope "$source_scope" \ - ' - .name |= sub($source_scope; "\($npm_scope)/") | - .version |= (split("-")[0] + "-preview-\($hash)") - ' \ - "$manifest_file" > temp.json - mv temp.json "$manifest_file" -} - -# Add resolutions to the root manifest so that imports under the source scope -# continue to resolve from the local workspace after packages are renamed to -# the preview scope. Without this, yarn resolves them from the npm registry, -# which causes build failures when workspace packages contain changes not yet -# published. -echo "Adding workspace resolutions to root manifest..." -resolutions="$(yarn workspaces list --no-private --json \ - | jq --slurp 'reduce .[] as $pkg ({}; .[$pkg.name] = "portal:./" + $pkg.location)')" -jq --argjson resolutions "$resolutions" '.resolutions = ((.resolutions // {}) + $resolutions)' package.json > temp.json -mv temp.json package.json - -echo "Preparing manifests..." -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([.location, .name]) | map(@tsv) | .[]') - -echo "Installing dependencies..." -yarn install --no-immutable diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index 865eb61a..3e1384d0 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -100,32 +100,47 @@ jobs: id: commit-sha run: echo "COMMIT_SHA=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - - name: Prepare preview builds (monorepo) - if: ${{ inputs.is-monorepo }} - uses: MetaMask/github-tools/.github/actions/prepare-preview-builds@prepare-preview-builds-action - with: - npm-scope: ${{ inputs.npm-scope }} - commit-hash: ${{ steps.commit-sha.outputs.COMMIT_SHA }} - source-scope: ${{ inputs.source-scope }} - - - name: Prepare preview builds (polyrepo) - if: ${{ !inputs.is-monorepo }} - run: | - jq \ - --arg npm_scope "$NPM_SCOPE" \ - --arg hash "$COMMIT_SHA" \ - --arg source_scope "$SOURCE_SCOPE" \ - ' - .name |= sub($source_scope; "\($npm_scope)/") | - .version |= (split("-")[0] + "-preview-\($hash)") - ' \ - package.json > temp.json - mv temp.json package.json - yarn install --no-immutable + - name: Prepare preview builds env: NPM_SCOPE: ${{ inputs.npm-scope }} COMMIT_SHA: ${{ steps.commit-sha.outputs.COMMIT_SHA }} SOURCE_SCOPE: ${{ inputs.source-scope }} + IS_MONOREPO: ${{ inputs.is-monorepo }} + run: | + prepare_manifest() { + local manifest_file="$1" + jq --raw-output \ + --arg npm_scope "$NPM_SCOPE" \ + --arg hash "$COMMIT_SHA" \ + --arg source_scope "$SOURCE_SCOPE" \ + ' + .name |= sub($source_scope; "\($npm_scope)/") | + .version |= (split("-")[0] + "-preview-\($hash)") + ' \ + "$manifest_file" > temp.json + mv temp.json "$manifest_file" + } + + if [[ "$IS_MONOREPO" == "true" ]]; then + # Add resolutions so renamed packages still resolve from local workspace + echo "Adding workspace resolutions to root manifest..." + resolutions="$(yarn workspaces list --no-private --json \ + | jq --slurp 'reduce .[] as $pkg ({}; .[$pkg.name] = "portal:./" + $pkg.location)')" + jq --argjson resolutions "$resolutions" '.resolutions = ((.resolutions // {}) + $resolutions)' package.json > temp.json + mv temp.json package.json + + echo "Preparing manifests..." + while IFS=$'\t' read -r location name; do + echo "- $name" + prepare_manifest "$location/package.json" + done < <(yarn workspaces list --no-private --json | jq --slurp --raw-output 'map([.location, .name]) | map(@tsv) | .[]') + else + echo "Preparing manifest..." + prepare_manifest package.json + fi + + echo "Installing dependencies..." + yarn install --no-immutable - name: Build run: ${{ inputs.build-command }} From 4ae7a7f0e4211e59c1e2d6ce13a13dcacaf65ced Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Tue, 17 Mar 2026 14:37:58 +0100 Subject: [PATCH 16/19] fix: improve preview builds docs link wording --- .github/workflows/publish-preview.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index 3e1384d0..a50a9751 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -254,7 +254,7 @@ jobs: run: | docs_link="" if [[ -n "$DOCS_URL" ]]; then - docs_link="[See these instructions](${DOCS_URL}) for more information about preview builds." + docs_link="[Learn how to use preview builds in other projects.](${DOCS_URL})." fi if [[ "$IS_MONOREPO" == "true" ]]; then From 26c411acdcded808553b3a70fd95cdc8051017b7 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Tue, 17 Mar 2026 14:51:19 +0100 Subject: [PATCH 17/19] fix: move period outside link text in docs link --- .github/workflows/publish-preview.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index a50a9751..d764685f 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -254,7 +254,7 @@ jobs: run: | docs_link="" if [[ -n "$DOCS_URL" ]]; then - docs_link="[Learn how to use preview builds in other projects.](${DOCS_URL})." + docs_link="[Learn how to use preview builds in other projects](${DOCS_URL})." fi if [[ "$IS_MONOREPO" == "true" ]]; then From 71a65779d564985ad430bde7acb023e5d5668ae5 Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Tue, 17 Mar 2026 15:23:46 +0100 Subject: [PATCH 18/19] fix: pin npm registry in publish steps to prevent .yarnrc.yml override --- .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 d764685f..5480dec6 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -236,12 +236,14 @@ jobs: run: yarn workspaces foreach --no-private --all exec yarn npm publish --tag preview env: YARN_NPM_AUTH_TOKEN: ${{ secrets.PUBLISH_PREVIEW_NPM_TOKEN }} + YARN_NPM_REGISTRY_SERVER: 'https://registry.npmjs.org' - name: Publish preview builds (polyrepo) if: ${{ !inputs.is-monorepo && !inputs.dry-run }} run: yarn npm publish --tag preview env: YARN_NPM_AUTH_TOKEN: ${{ secrets.PUBLISH_PREVIEW_NPM_TOKEN }} + YARN_NPM_REGISTRY_SERVER: 'https://registry.npmjs.org' - name: Dry run notice if: ${{ inputs.dry-run }} From 4c4c413994cc9ba6b0c29f657a53e6acb073c4ee Mon Sep 17 00:00:00 2001 From: Salah-Eddine Saakoun Date: Tue, 17 Mar 2026 15:42:32 +0100 Subject: [PATCH 19/19] fix: add contents:read permission to publish-preview job --- .github/workflows/publish-preview.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish-preview.yml b/.github/workflows/publish-preview.yml index 5480dec6..60236360 100644 --- a/.github/workflows/publish-preview.yml +++ b/.github/workflows/publish-preview.yml @@ -177,6 +177,7 @@ jobs: name: Publish preview needs: build-preview permissions: + contents: read pull-requests: write environment: ${{ inputs.environment }} runs-on: ubuntu-latest