Changelog added, pkg version bumbed #7417
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Checks (SDK Pod) | |
| on: | |
| # Plain `pull_request` (NOT pull_request_target): fork PRs run with a | |
| # read-only token and no secrets, so it is safe to check out and execute PR | |
| # code (install/lint/build/test). These checks need no secrets, so the | |
| # privileged context would buy nothing and only add "pwn request" risk. | |
| # | |
| # No trigger-level `paths:` filter on purpose: this workflow publishes the | |
| # required "SDK Pod Checks" status, which must report on EVERY PR or GitHub | |
| # blocks unrelated PRs ("waiting for status to be reported"). The single job | |
| # below detects SDK pod changes itself and is a fast no-op when nothing | |
| # relevant changed. | |
| pull_request: | |
| types: | |
| - opened | |
| - synchronize | |
| - reopened | |
| - labeled | |
| branches: | |
| - main | |
| - release-* | |
| - feature-* | |
| - tmp-* | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| # Package config lives in .github/sdk-pod-checks.json | |
| # Scripts (lint, build, test:unit) are auto-detected from each package's package.json | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} | |
| cancel-in-progress: true | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # Single required status check for SDK pod packages. | |
| # | |
| # Runs on every PR so the "SDK Pod Checks" context is always reported — a | |
| # required check that is never reported blocks the PR forever. It is a fast | |
| # no-op (detect step only) when no SDK pod files changed, so unrelated PRs are | |
| # unaffected. Mark THIS job's name ("SDK Pod Checks") required in the ruleset. | |
| # | |
| # - no SDK pod files changed -> pass (no checks run) | |
| # - changed + checks pass -> pass | |
| # - changed + a check fails -> fail (blocks the merge) | |
| # - changed + a check fails, but the 'skip-sdk-pod-checks' label is set | |
| # -> pass (audited override for e.g. a | |
| # confirmed false-positive test; the | |
| # failure is still run and logged) | |
| # | |
| # The package config and the diff baseline are read from the trusted base | |
| # commit (not the PR), so a PR cannot edit the config to skip its own checks. | |
| # Packages are checked sequentially in this single job (instead of a parallel | |
| # matrix) so that exactly one check appears on every PR. | |
| # --------------------------------------------------------------------------- | |
| sdk-pod-checks: | |
| name: SDK Pod Checks | |
| runs-on: ubuntu-latest | |
| # Hard cap so a hung job can't hog runners or burn minutes. Slightly above | |
| # the 30-min default because this rollup runs up to 6 packages sequentially | |
| # (incl. the SDK build + bare/e2e tests and the two consumer installs). | |
| timeout-minutes: 45 | |
| steps: | |
| # Checkout the trusted base commit. The package config and the diff | |
| # baseline are read from here — never from the PR — so a PR cannot edit | |
| # the config to exclude itself from its own checks. | |
| - name: Checkout base | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2 | |
| with: | |
| ref: ${{ github.event.pull_request.base.sha || github.ref }} | |
| fetch-depth: 0 | |
| - name: Detect changed SDK pod packages | |
| id: detect | |
| timeout-minutes: 5 | |
| shell: bash | |
| env: | |
| EVENT: ${{ github.event_name }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| BASE_REF: ${{ github.event.pull_request.base.ref }} | |
| run: | | |
| # Fail closed: any error here (git fetch/diff, jq) fails the step and | |
| # therefore the whole "SDK Pod Checks" job — never a silent green. | |
| set -euo pipefail | |
| # Default optional fields. `sources` is which SDK variants to test a | |
| # package against: sdk_sources_release on release-* PRs (and dispatch), | |
| # else sdk_sources. Both default to ["default"] — one leg, no prep. | |
| ALL_PACKAGES=$(jq -c --arg event "$EVENT" --arg base "${BASE_REF:-}" ' | |
| [ .[] | |
| | .pkg_manager //= "npm" | |
| | .needs_bare //= false | |
| | .tests_bare //= false | |
| | .sdk_sources //= ["default"] | |
| | .sdk_sources_release //= .sdk_sources | |
| | .sources = (if ($event == "workflow_dispatch" or ($base | startswith("release-"))) | |
| then .sdk_sources_release else .sdk_sources end) | |
| ]' .github/sdk-pod-checks.json) | |
| # Gates the bare-sdk dep-parity check to dep/version changes only. | |
| sdk_pkgjson_changed="false" | |
| if [ "$EVENT" = "workflow_dispatch" ]; then | |
| # workflow_dispatch: run all packages | |
| PACKAGES="$ALL_PACKAGES" | |
| sdk_pkgjson_changed="true" | |
| else | |
| # Fetch PR head (available on the repo via pull refs) | |
| git fetch origin "refs/pull/${PR_NUMBER}/head" | |
| # Filter to only packages with changed files in this PR | |
| CHANGED_FILES=$(git diff --name-only "$BASE_SHA"..."$HEAD_SHA") | |
| PACKAGES=$(echo "$ALL_PACKAGES" | jq -c --arg files "$CHANGED_FILES" '[ | |
| .[] | select(.path as $p | $files | split("\n") | any(startswith($p + "/"))) | |
| ]') | |
| if echo "$CHANGED_FILES" | grep -qx "packages/sdk/package.json"; then | |
| sdk_pkgjson_changed="true" | |
| fi | |
| fi | |
| echo "packages=${PACKAGES}" >> "$GITHUB_OUTPUT" | |
| echo "sdk_pkgjson_changed=${sdk_pkgjson_changed}" >> "$GITHUB_OUTPUT" | |
| has_changes=$(echo "$PACKAGES" | jq -r 'if length > 0 then "true" else "false" end') | |
| echo "has_changes=${has_changes}" >> "$GITHUB_OUTPUT" | |
| if [ "$has_changes" = "true" ]; then | |
| echo "::notice::SDK pod packages changed: $(echo "$PACKAGES" | jq -r '[.[].package] | join(", ")')" | |
| else | |
| echo "::notice::No SDK pod package files changed - gate passes." | |
| fi | |
| # ----- Everything below runs only when SDK pod files changed ----- | |
| # Safe under `pull_request`: fork PRs get a read-only token and no secrets, | |
| # so checking out and executing PR code cannot exfiltrate anything. | |
| - name: Checkout PR head | |
| if: steps.detect.outputs.has_changes == 'true' | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2 | |
| with: | |
| ref: ${{ github.event.pull_request.head.sha || github.ref }} | |
| - name: Setup Bun | |
| if: steps.detect.outputs.has_changes == 'true' | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # 2.2.0 | |
| with: | |
| bun-version: latest | |
| - name: Setup Node | |
| if: steps.detect.outputs.has_changes == 'true' | |
| uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # 6.3.0 | |
| with: | |
| node-version: 22 | |
| - name: Run SDK pod checks | |
| if: steps.detect.outputs.has_changes == 'true' | |
| timeout-minutes: 40 | |
| shell: bash | |
| env: | |
| PACKAGES: ${{ steps.detect.outputs.packages }} | |
| SDK_PKGJSON_CHANGED: ${{ steps.detect.outputs.sdk_pkgjson_changed }} | |
| # Maintainer escape hatch: when this label is present the gate still | |
| # runs and logs failures, but reports success so an urgent fix can | |
| # land over a confirmed false-positive check. Audited via the label | |
| # and the warning below. | |
| OVERRIDE: ${{ contains(github.event.pull_request.labels.*.name, 'skip-sdk-pod-checks') }} | |
| run: | | |
| set -uo pipefail | |
| WS="$GITHUB_WORKSPACE" | |
| fail=0 | |
| # Run a single check; record (don't abort on) failures so every check runs. | |
| run() { | |
| local label="$1" | |
| shift | |
| if "$@"; then | |
| echo " ok [$PKG] $label" | |
| else | |
| echo "::error::[$PKG] $label failed" | |
| fail=$((fail + 1)) | |
| fi | |
| } | |
| # Validates the SDK tarball installs cleanly for end consumers: | |
| # 1. zero peer-dependency warnings on install | |
| # 2. shared P2P packages resolve to a single copy | |
| # 3. import('@qvac/sdk') resolves from a vanilla install | |
| # Runs in a subshell so its directory changes do not leak. | |
| consumer_install_check() ( | |
| set -eo pipefail | |
| cd "$WS/packages/sdk" | |
| # Pack the tarball produced by the preceding build step. | |
| bun pm pack --destination dist/ | |
| tarball=$(ls dist/qvac-sdk-*.tgz | head -n1) | |
| test -n "$tarball" || { echo "::error::No SDK tarball found after pack"; exit 1; } | |
| tarball_abs="$(pwd)/$tarball" | |
| check_consumer() { | |
| local consumer="$1" label="$2" | |
| cd "$consumer" | |
| if grep -Eq "ERESOLVE|npm warn peer" install.log; then | |
| echo "::error title=Peer dependency drift (${label})::SDK consumer install surfaced peer warnings" | |
| grep -E "ERESOLVE|npm warn peer" install.log || true | |
| return 1 | |
| fi | |
| echo "::notice::[${label}] 0 peer warnings" | |
| local f=0 tree copies | |
| for pkg in corestore hyperswarm hyperdrive hyperdb hyperblobs hyperdht; do | |
| tree=$(npm ls "$pkg" --all 2>&1) | |
| copies=$(printf '%s\n' "$tree" | grep -E "[─ ]${pkg}@" | grep -vc "deduped" || true) | |
| if [ "$copies" != "1" ]; then | |
| echo "::error::[${label}] ${pkg} resolved to ${copies} copies (expected 1)" | |
| printf '%s\n' "$tree" | |
| f=1 | |
| else | |
| echo " ok [${label}] ${pkg} = 1 copy" | |
| fi | |
| done | |
| [ "$f" = "0" ] || return 1 | |
| echo "::notice::[${label}] Single-copy invariant holds for shared P2P packages" | |
| node -e "import('@qvac/sdk').then(m => { if (Object.keys(m).length < 50) { console.error('FAIL: too few exports'); process.exit(1); } console.log('[${label}] import ok:', Object.keys(m).length, 'exports'); }).catch(e => { console.error('[${label}] FAIL:', e.message); process.exit(1); })" | |
| } | |
| # Scenario 1: default install (plug-n-play) - all optionalDependencies present. | |
| consumer_default=$(mktemp -d) | |
| cd "$consumer_default" | |
| npm init -y > /dev/null | |
| npm pkg set type=module > /dev/null | |
| npm install --no-fund --no-audit --ignore-scripts --loglevel=info "$tarball_abs" 2>&1 | tee install.log | |
| check_consumer "$consumer_default" "default" | |
| # Scenario 2: lean backend install (--omit=optional) - no optionalDependencies. | |
| consumer_lean=$(mktemp -d) | |
| cd "$consumer_lean" | |
| npm init -y > /dev/null | |
| npm pkg set type=module > /dev/null | |
| npm install --no-fund --no-audit --ignore-scripts --omit=optional --loglevel=info "$tarball_abs" 2>&1 | tee install.log | |
| check_consumer "$consumer_lean" "lean (--omit=optional)" | |
| ) | |
| # Install the Bare runtime once if any changed package needs it. | |
| if echo "$PACKAGES" | jq -e 'any(.[]; .needs_bare == true)' > /dev/null; then | |
| echo "::group::Install Bare runtime" | |
| npm install -g --force bare | |
| echo "::endgroup::" | |
| fi | |
| len=$(echo "$PACKAGES" | jq 'length') | |
| i=0 | |
| while [ "$i" -lt "$len" ]; do | |
| PKG=$(echo "$PACKAGES" | jq -r ".[$i].package") | |
| P_PATH=$(echo "$PACKAGES" | jq -r ".[$i].path") | |
| PM=$(echo "$PACKAGES" | jq -r ".[$i].pkg_manager") | |
| TESTS_BARE=$(echo "$PACKAGES" | jq -r ".[$i].tests_bare") | |
| SOURCES=$(echo "$PACKAGES" | jq -r ".[$i].sources[]") | |
| i=$((i + 1)) | |
| echo "::group::SDK pod checks - $PKG" | |
| cd "$WS/$P_PATH" | |
| # Disallowed dependencies: no git URLs or dev/tmp versions. Checked on | |
| # the committed manifest, before any per-source prep can rewrite it. | |
| deps=$(jq -r '([.dependencies, .devDependencies] | map(select(type=="object")) | add // {}) | to_entries[] | .value' package.json) | |
| if echo "$deps" | grep -Eq '^(git\+https:\/\/github.com|[0-9]+\.[0-9]+\.[0-9]+-(dev|tmp)[^"]*)$'; then | |
| echo "::error::[$PKG] disallowed dependency detected (git URL or dev/tmp version)" | |
| fail=$((fail + 1)) | |
| fi | |
| # Run the checks once per declared SDK source. The optional | |
| # sdk-source:<source> script does that source's setup (e.g. link the | |
| # in-repo SDK); absent = plain install of the committed dependency. | |
| src_idx=0 | |
| for SRC in $SOURCES; do | |
| # Bracketed source shown after each check name, e.g. "build [workspace]". | |
| # Empty for the lone "default" source (single-leg packages stay clean). | |
| src="" | |
| if [ "$SRC" != "default" ]; then src="[$SRC]"; fi | |
| # Reset to the committed manifest. For the 2nd+ source also wipe | |
| # node_modules + lockfile — a prior `npm install ../sdk` leaves a | |
| # symlink npm would keep, leaking that source into this one. | |
| git checkout HEAD -- package.json 2>/dev/null || true | |
| if [ "$src_idx" -gt 0 ]; then | |
| rm -rf node_modules package-lock.json | |
| fi | |
| # npm run --if-present reads package.json scripts and works regardless | |
| # of the installer (bun lacks --if-present). | |
| if [ "$PKG" = "sdk" ]; then | |
| run "install $src" bun install | |
| else | |
| run "install $src" "$PM" install | |
| fi | |
| # Per-source setup (no-op if the script is absent). | |
| run "sdk-source $src" npm run --if-present "sdk-source:$SRC" | |
| run "lint $src" npm run --if-present lint | |
| run "build:types $src" npm run --if-present build:types | |
| run "build $src" npm run --if-present build | |
| run "test:unit $src" npm run --if-present test:unit | |
| if [ "$TESTS_BARE" = "true" ]; then | |
| run "test:bare $src" npm run --if-present test:bare | |
| fi | |
| run "test:e2e $src" npm run --if-present test:e2e | |
| src_idx=$((src_idx + 1)) | |
| done | |
| # Back to the committed manifest for the sdk-only checks below. | |
| git checkout HEAD -- package.json 2>/dev/null || true | |
| if [ "$PKG" = "sdk" ]; then | |
| # Assert bare-sdk stays in dep lockstep with sdk. | |
| if [ "$SDK_PKGJSON_CHANGED" = "true" ]; then | |
| run "bare-sdk deps parity" node "$WS/packages/bare-sdk/scripts/check-deps-vs-sdk.mjs" | |
| fi | |
| if consumer_install_check; then | |
| echo " ok [$PKG] consumer install" | |
| else | |
| echo "::error::[$PKG] consumer install check failed" | |
| fail=$((fail + 1)) | |
| fi | |
| fi | |
| cd "$WS" | |
| echo "::endgroup::" | |
| done | |
| if [ "$fail" -gt 0 ]; then | |
| if [ "$OVERRIDE" = "true" ]; then | |
| echo "::warning::$fail SDK pod check(s) failed, but the 'skip-sdk-pod-checks' label is applied - overriding to allow this merge (e.g. a confirmed false-positive test). Fix the failing check in a follow-up." | |
| exit 0 | |
| fi | |
| echo "::error::There were $fail failed SDK pod check(s)" | |
| exit 1 | |
| fi | |
| echo "All SDK pod checks passed." |