From 5e75da61810237c1175da8af10ac3baea6f6680b Mon Sep 17 00:00:00 2001 From: rishigupta1599 Date: Thu, 18 Jun 2026 23:07:42 +0530 Subject: [PATCH 1/3] ci(executable): gate executable build+verify per PR and make verify actually fail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related fixes to the executable pipeline: 1. The release "Verify executable" step ran a bare `./percy --version`. The executables are built on Node 14, where an unhandled promise rejection is a non-fatal warning (exit 0), so a binary that crashes on startup still exits 0 — the check passed and the broken binary was uploaded anyway. Replace it with scripts/verify-executable.sh, which fails unless `--version` exits 0, prints a real semver, and emits no runtime-error markers (used by both the macOS and Windows verify steps). 2. The build only ran on `release: published`, so a binary-breaking change was only caught after merge, at release time. Add a `pull_request` workflow (executable-check.yml) that builds the executables and runs the same verify, with no signing and no upload, so such PRs fail CI before merge. scripts/executable.sh now skips the macOS signing/notarization/upload block when APPLE_DEV_CERT is unset (PR builds, incl. forks), leaving ./percy as the macOS binary so the verify step runs natively. The signed release path is unchanged. Note: making this block merges requires marking "Build & verify executable" as a required status check in branch protection. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/executable-check.yml | 33 ++++++++++++++++ .github/workflows/executable.yml | 5 ++- scripts/executable.sh | 52 +++++++++++++++----------- scripts/verify-executable.sh | 45 ++++++++++++++++++++++ 4 files changed, 112 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/executable-check.yml create mode 100755 scripts/verify-executable.sh diff --git a/.github/workflows/executable-check.yml b/.github/workflows/executable-check.yml new file mode 100644 index 000000000..2a4411dd4 --- /dev/null +++ b/.github/workflows/executable-check.yml @@ -0,0 +1,33 @@ +name: Verify Executable + +# Per-PR gate: build the packaged executables exactly as the release pipeline +# does and smoke-test them — but without signing, notarizing or uploading. This +# catches binary-only breakages (e.g. a bad require produced by the CJS +# transpile that never shows up in the Node/source test suite) on the PR that +# introduces them, so they can't reach a release. +# +# No secrets are used or needed: scripts/executable.sh skips the macOS +# signing/notarization block when APPLE_DEV_CERT is unset, which also makes this +# safe to run on pull requests from forks. +on: + pull_request: + branches: [master] + +concurrency: + group: verify-executable-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify: + name: Build & verify executable + runs-on: macos-latest + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v4 + with: + node-version: 14 + architecture: x64 + - name: Build executables (no signing, no upload) + run: ./scripts/executable.sh + - name: Verify executable + run: ./scripts/verify-executable.sh ./percy diff --git a/.github/workflows/executable.yml b/.github/workflows/executable.yml index 44de17fc8..399b7a360 100644 --- a/.github/workflows/executable.yml +++ b/.github/workflows/executable.yml @@ -20,7 +20,7 @@ jobs: APPLE_CERT_KEY: ${{secrets.APPLE_CERT_KEY}} APPLE_TEAM_ID: ${{secrets.APPLE_TEAM_ID}} - name: Verify executable - run: ./percy --version + run: ./scripts/verify-executable.sh ./percy - name: Upload win artifact uses: actions/upload-artifact@v4 with: @@ -93,7 +93,8 @@ jobs: Remove-Item -Force gcp-sa-key.json -ErrorAction SilentlyContinue Remove-Item -Force comodo_signing_cert.crt -ErrorAction SilentlyContinue - name: Verify executable - run: ./percy.exe --version + shell: bash + run: ./scripts/verify-executable.sh ./percy.exe - run: | powershell -Command "Compress-Archive -Path 'percy.exe' -DestinationPath 'percy-win.zip'" - name: Upload assets diff --git a/scripts/executable.sh b/scripts/executable.sh index 655a564bb..c4305765a 100755 --- a/scripts/executable.sh +++ b/scripts/executable.sh @@ -52,24 +52,34 @@ mv run-linux percy && chmod +x percy mv run-macos percy-osx && chmod +x percy-osx mv run-win.exe percy.exe && chmod +x percy.exe -# Sign & Notrize mac app -echo "$APPLE_DEV_CERT" | base64 -d > AppleDevIDApp.p12 - -security create-keychain -p percy percy.keychain -security import AppleDevIDApp.p12 -t agg -k percy.keychain -P $APPLE_CERT_KEY -A -security list-keychains -s ~/Library/Keychains/percy.keychain -security default-keychain -s ~/Library/Keychains/percy.keychain -security unlock-keychain -p "percy" ~/Library/Keychains/percy.keychain -security set-keychain-settings -t 3600 -l ~/Library/Keychains/percy.keychain -security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k percy ~/Library/Keychains/percy.keychain-db - -codesign --force --verbose=4 -s "Developer ID Application: BrowserStack Inc ($APPLE_TEAM_ID)" --options runtime --entitlements scripts/files/entitlement.plist --keychain ~/Library/Keychains/percy.keychain percy-osx - -# Create zip file for uploading as assets -zip percy-linux.zip percy -mv percy-osx percy -zip percy-osx.zip percy - -xcrun notarytool submit --apple-id "$APPLE_ID_USERNAME" --password $APPLE_ID_KEY --team-id $APPLE_TEAM_ID percy-osx.zip --wait - -cleanup +# Sign, notarize and package the assets only when the Apple signing secrets are +# present. Pull-request builds run without secrets: there we only want to prove +# the executables build and run (the verify step), not sign or ship them. +if [ -n "${APPLE_DEV_CERT:-}" ]; then + # Sign & Notrize mac app + echo "$APPLE_DEV_CERT" | base64 -d > AppleDevIDApp.p12 + + security create-keychain -p percy percy.keychain + security import AppleDevIDApp.p12 -t agg -k percy.keychain -P $APPLE_CERT_KEY -A + security list-keychains -s ~/Library/Keychains/percy.keychain + security default-keychain -s ~/Library/Keychains/percy.keychain + security unlock-keychain -p "percy" ~/Library/Keychains/percy.keychain + security set-keychain-settings -t 3600 -l ~/Library/Keychains/percy.keychain + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k percy ~/Library/Keychains/percy.keychain-db + + codesign --force --verbose=4 -s "Developer ID Application: BrowserStack Inc ($APPLE_TEAM_ID)" --options runtime --entitlements scripts/files/entitlement.plist --keychain ~/Library/Keychains/percy.keychain percy-osx + + # Create zip file for uploading as assets + zip percy-linux.zip percy + mv percy-osx percy + zip percy-osx.zip percy + + xcrun notarytool submit --apple-id "$APPLE_ID_USERNAME" --password $APPLE_ID_KEY --team-id $APPLE_TEAM_ID percy-osx.zip --wait + + cleanup +else + echo "APPLE_DEV_CERT not set — skipping macOS signing/notarization (PR build)." + # Leave ./percy as the macOS binary so the verify step runs natively on the + # macOS runner (mirrors the signed path, which ends with percy == percy-osx). + mv percy-osx percy +fi diff --git a/scripts/verify-executable.sh b/scripts/verify-executable.sh new file mode 100755 index 000000000..fbc7cba35 --- /dev/null +++ b/scripts/verify-executable.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Smoke-test a packaged percy executable. +# +# Why this isn't just `./percy --version`: the executables are built on Node 14, +# where an unhandled promise rejection is reported as a *warning* and the process +# still exits 0. So a binary that throws on startup (e.g. a bad require produced +# by the CJS transpile) prints a stack trace yet a bare `--version` exit-code +# check passes — and the release pipeline happily uploads a broken binary. +# +# This script treats the binary as broken if `--version` either exits non-zero, +# fails to print a real version, or emits any runtime-error marker on stdout/stderr. +# +# Usage: scripts/verify-executable.sh [path-to-binary] (default: ./percy) +set -u -o pipefail + +BIN="${1:-./percy}" +echo "Verifying: $BIN --version" + +# Capture stdout+stderr together; keep the exit code without tripping set -e. +output="$("$BIN" --version 2>&1)" +status=$? + +echo "----- output -----" +echo "$output" +echo "------------------" + +if [ "$status" -ne 0 ]; then + echo "::error::'$BIN --version' exited with status $status" + exit 1 +fi + +# Node 14 turns startup crashes into non-fatal warnings, so scan the output for +# the error signatures a broken binary leaves behind. +if echo "$output" | grep -qiE 'UnhandledPromiseRejection|is not a function|TypeError|ReferenceError|SyntaxError|Cannot find module|Error:'; then + echo "::error::'$BIN --version' emitted a runtime error (binary is broken)" + exit 1 +fi + +# A healthy binary prints its semver. If it crashed before printing one, fail. +if ! echo "$output" | grep -qE '[0-9]+\.[0-9]+\.[0-9]+'; then + echo "::error::'$BIN --version' did not print a valid version string" + exit 1 +fi + +echo "OK: $BIN is healthy" From e9ae6374c4e6c063e895bdcd4e3ea63dc0f0b1f1 Mon Sep 17 00:00:00 2001 From: rishigupta1599 Date: Thu, 18 Jun 2026 23:13:09 +0530 Subject: [PATCH 2/3] ci(executable): drop pkg --debug flag to silence per-file packaging logs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pkg ... -d` enables pkg's debug mode, which logs every bundled file ("included as DISCLOSED code (with sources)" / "asset content") — thousands of lines that drown the verify output. The flag only affects logging, not the produced binaries, so remove it. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/executable.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/executable.sh b/scripts/executable.sh index c4305765a..befcb2338 100755 --- a/scripts/executable.sh +++ b/scripts/executable.sh @@ -44,8 +44,10 @@ gsed -i '/Update NODE_ENV for executable/{s//\nprocess.env.NODE_ENV = "executabl npm run build_cjs cp -R ./build/* packages/ -# Create executables -pkg ./packages/cli/bin/run.js -d +# Create executables. (No `-d`/`--debug`: it only adds per-file "included as +# DISCLOSED code / asset content" logging — thousands of lines — without +# changing the output binaries.) +pkg ./packages/cli/bin/run.js # Rename executables mv run-linux percy && chmod +x percy From e32e1aa53fc95f6593e95e95c7308250801502db Mon Sep 17 00:00:00 2001 From: rishigupta1599 <114384996+rishigupta1599@users.noreply.github.com> Date: Thu, 18 Jun 2026 23:16:46 +0530 Subject: [PATCH 3/3] Potential fix for pull request finding 'CodeQL / Workflow does not contain permissions' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/executable-check.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/executable-check.yml b/.github/workflows/executable-check.yml index 2a4411dd4..c3f320eb5 100644 --- a/.github/workflows/executable-check.yml +++ b/.github/workflows/executable-check.yml @@ -13,6 +13,9 @@ on: pull_request: branches: [master] +permissions: + contents: read + concurrency: group: verify-executable-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true