From 0526f489c427ae6e89dd0bf7adc823bd1fa75e5c Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Tue, 31 Mar 2026 18:44:51 -0400 Subject: [PATCH 1/9] chore: upload posthog-js dist to S3 on release --- .github/scripts/upload-posthog-js-s3.sh | 62 ++++++++++++++++++++++ .github/workflows/release.yml | 69 ++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100755 .github/scripts/upload-posthog-js-s3.sh diff --git a/.github/scripts/upload-posthog-js-s3.sh b/.github/scripts/upload-posthog-js-s3.sh new file mode 100755 index 0000000000..8523cc72f1 --- /dev/null +++ b/.github/scripts/upload-posthog-js-s3.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# +# Upload posthog-js dist artifacts to S3 and append the version to versions.json. +# +# Usage: +# VERSION=1.365.0 ./upload-posthog-js-s3.sh +# +# VERSION must be set as an environment variable (not an argument) to avoid +# shell injection if the value were ever attacker-influenced. +# +# Expects AWS credentials to be configured before invocation. +# +set -euo pipefail + +BUCKET="${1:?Usage: VERSION=x.y.z $0 }" +DIST_DIR="packages/browser/dist" + +if [[ -z "${VERSION:-}" ]]; then + echo "ERROR: VERSION environment variable is required" >&2 + exit 1 +fi + +# Validate version is strict semver (e.g. 1.365.0 or 1.365.0-beta.1). +# Prevents path traversal — no slashes, dots only in expected positions. +if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-][a-zA-Z0-9.]+)?$ ]]; then + echo "ERROR: Invalid version format: '$VERSION'" >&2 + exit 1 +fi + +echo "==> Uploading posthog-js v$VERSION to s3://$BUCKET/$VERSION/" +aws s3 cp "$DIST_DIR/" "s3://$BUCKET/$VERSION/" \ + --recursive \ + --exclude "*" \ + --include "*.js" \ + --cache-control "public, max-age=31536000, immutable" \ + --content-type "application/javascript" + +echo "==> Updating versions.json in s3://$BUCKET/" +TMPDIR="$(mktemp -d)" +trap 'rm -rf "$TMPDIR"' EXIT + +# Distinguish "file doesn't exist" from real errors (auth, network). +# A blind fallback to '[]' on any error would silently drop all previous versions. +if aws s3 cp "s3://$BUCKET/versions.json" "$TMPDIR/versions.json"; then + echo "Downloaded existing versions.json" +elif aws s3api head-object --bucket "$BUCKET" --key "versions.json" 2>/dev/null; then + echo "ERROR: versions.json exists but could not be downloaded" >&2 + exit 1 +else + echo "No existing versions.json found, starting fresh" + echo '[]' > "$TMPDIR/versions.json" +fi + +if jq -e --arg v "$VERSION" '.[] | select(.version == $v)' "$TMPDIR/versions.json" > /dev/null 2>&1; then + echo "Version $VERSION already in versions.json, skipping" +else + jq --arg v "$VERSION" --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + '. + [{"version": $v, "timestamp": $ts}]' "$TMPDIR/versions.json" > "$TMPDIR/versions_updated.json" + aws s3 cp "$TMPDIR/versions_updated.json" "s3://$BUCKET/versions.json" \ + --content-type "application/json" + echo "Added v$VERSION to versions.json" +fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e6397e7770..499b176e31 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -334,9 +334,76 @@ jobs: message: '❌ Failed to release `${{ matrix.package.name }}@v${{ steps.check-package-version.outputs.committed-version }}`! ' emoji_reaction: 'x' + upload-s3: + name: Upload posthog-js dist to S3 + needs: [version-bump, publish, notify-approval-needed] + runs-on: ubuntu-latest + # Run as long as the version bump committed — even if some matrix publishes failed, + # posthog-js might have succeeded and we still want the artifacts in S3. + if: always() && needs.version-bump.outputs.commit-hash != '' + environment: 'S3 Upload' # OIDC role scoped to this environment — must exist in repo settings with required reviewers + permissions: + contents: read + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + ref: ${{ needs.version-bump.outputs.commit-hash }} + fetch-depth: 0 + + - name: Check posthog-js version + id: check-version + uses: PostHog/check-package-version@v2.1.0 + with: + path: packages/browser + + - name: Setup environment + if: steps.check-version.outputs.is-new-version == 'true' + uses: ./.github/actions/setup + + # Upload to US (us-east-1) + - name: Configure AWS credentials (US) + if: steps.check-version.outputs.is-new-version == 'true' + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ vars.AWS_S3_UPLOAD_ROLE_ARN_US }} + aws-region: us-east-1 + + - name: Upload dist and update manifest (US) + if: steps.check-version.outputs.is-new-version == 'true' + env: + VERSION: ${{ steps.check-version.outputs.committed-version }} + run: .github/scripts/upload-posthog-js-s3.sh posthog-js-prod-us + + # Upload to EU (eu-central-1) + - name: Configure AWS credentials (EU) + if: steps.check-version.outputs.is-new-version == 'true' + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ vars.AWS_S3_UPLOAD_ROLE_ARN_EU }} + aws-region: eu-central-1 + + - name: Upload dist and update manifest (EU) + if: steps.check-version.outputs.is-new-version == 'true' + env: + VERSION: ${{ steps.check-version.outputs.committed-version }} + run: .github/scripts/upload-posthog-js-s3.sh posthog-js-prod-eu + + - name: Notify Slack - S3 Upload Failed + continue-on-error: true + if: ${{ failure() && needs.notify-approval-needed.outputs.slack_ts != '' }} + uses: PostHog/.github/.github/actions/slack-thread-reply@main + with: + slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }} + slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }} + thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }} + message: '❌ Failed to upload posthog-js v${{ steps.check-version.outputs.committed-version }} dist to S3! ' + emoji_reaction: 'x' + notify-released: name: Notify Slack - Released - needs: [notify-approval-needed, publish] + needs: [notify-approval-needed, publish, upload-s3] runs-on: ubuntu-latest if: always() && needs.publish.result == 'success' && needs.notify-approval-needed.outputs.slack_ts != '' steps: From 09f9d9d39d8d9687c8976c507c8d03ed9f37d4c1 Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Wed, 1 Apr 2026 05:38:18 -0400 Subject: [PATCH 2/9] chore: pin aws-actions/configure-aws-credentials to SHA --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 499b176e31..d197332a47 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -365,7 +365,7 @@ jobs: # Upload to US (us-east-1) - name: Configure AWS credentials (US) if: steps.check-version.outputs.is-new-version == 'true' - uses: aws-actions/configure-aws-credentials@v4 + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6 with: role-to-assume: ${{ vars.AWS_S3_UPLOAD_ROLE_ARN_US }} aws-region: us-east-1 @@ -379,7 +379,7 @@ jobs: # Upload to EU (eu-central-1) - name: Configure AWS credentials (EU) if: steps.check-version.outputs.is-new-version == 'true' - uses: aws-actions/configure-aws-credentials@v4 + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6 with: role-to-assume: ${{ vars.AWS_S3_UPLOAD_ROLE_ARN_EU }} aws-region: eu-central-1 From b064f9b7fcb51f9ee52103e61bd906182a2cd456 Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Wed, 1 Apr 2026 13:52:55 -0400 Subject: [PATCH 3/9] ci: split build and upload into distinct jobs --- .github/workflows/release.yml | 53 +++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d197332a47..d50025b09d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -334,17 +334,18 @@ jobs: message: '❌ Failed to release `${{ matrix.package.name }}@v${{ steps.check-package-version.outputs.committed-version }}`! ' emoji_reaction: 'x' - upload-s3: - name: Upload posthog-js dist to S3 + build-s3-artifacts: + name: Build posthog-js dist for S3 needs: [version-bump, publish, notify-approval-needed] runs-on: ubuntu-latest # Run as long as the version bump committed — even if some matrix publishes failed, # posthog-js might have succeeded and we still want the artifacts in S3. if: always() && needs.version-bump.outputs.commit-hash != '' - environment: 'S3 Upload' # OIDC role scoped to this environment — must exist in repo settings with required reviewers permissions: contents: read - id-token: write + outputs: + is-new-version: ${{ steps.check-version.outputs.is-new-version }} + committed-version: ${{ steps.check-version.outputs.committed-version }} steps: - name: Checkout repository uses: actions/checkout@v6 @@ -362,32 +363,60 @@ jobs: if: steps.check-version.outputs.is-new-version == 'true' uses: ./.github/actions/setup + - name: Upload dist artifact + if: steps.check-version.outputs.is-new-version == 'true' + uses: actions/upload-artifact@v4 + with: + name: posthog-js-dist + path: packages/browser/dist/*.js + retention-days: 1 + if-no-files-found: error + + upload-s3: + name: Upload posthog-js dist to S3 + needs: [build-s3-artifacts, version-bump, notify-approval-needed] + runs-on: ubuntu-latest + if: always() && needs.build-s3-artifacts.outputs.is-new-version == 'true' + environment: 'S3 Upload' # For OIDC credential scoping only — no required reviewers (single approval at NPM Release) + permissions: + contents: read + id-token: write + steps: + # Sparse checkout: only the upload script — no pnpm install/build runs here. + - name: Checkout upload script + uses: actions/checkout@v6 + with: + ref: ${{ needs.version-bump.outputs.commit-hash }} + sparse-checkout: .github/scripts + + - name: Download dist artifact + uses: actions/download-artifact@v4 + with: + name: posthog-js-dist + path: packages/browser/dist + # Upload to US (us-east-1) - name: Configure AWS credentials (US) - if: steps.check-version.outputs.is-new-version == 'true' uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6 with: role-to-assume: ${{ vars.AWS_S3_UPLOAD_ROLE_ARN_US }} aws-region: us-east-1 - name: Upload dist and update manifest (US) - if: steps.check-version.outputs.is-new-version == 'true' env: - VERSION: ${{ steps.check-version.outputs.committed-version }} + VERSION: ${{ needs.build-s3-artifacts.outputs.committed-version }} run: .github/scripts/upload-posthog-js-s3.sh posthog-js-prod-us # Upload to EU (eu-central-1) - name: Configure AWS credentials (EU) - if: steps.check-version.outputs.is-new-version == 'true' uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6 with: role-to-assume: ${{ vars.AWS_S3_UPLOAD_ROLE_ARN_EU }} aws-region: eu-central-1 - name: Upload dist and update manifest (EU) - if: steps.check-version.outputs.is-new-version == 'true' env: - VERSION: ${{ steps.check-version.outputs.committed-version }} + VERSION: ${{ needs.build-s3-artifacts.outputs.committed-version }} run: .github/scripts/upload-posthog-js-s3.sh posthog-js-prod-eu - name: Notify Slack - S3 Upload Failed @@ -398,14 +427,14 @@ jobs: slack_bot_token: ${{ secrets.SLACK_CLIENT_LIBRARIES_BOT_TOKEN }} slack_channel_id: ${{ vars.SLACK_APPROVALS_CLIENT_LIBRARIES_CHANNEL_ID }} thread_ts: ${{ needs.notify-approval-needed.outputs.slack_ts }} - message: '❌ Failed to upload posthog-js v${{ steps.check-version.outputs.committed-version }} dist to S3! ' + message: '❌ Failed to upload posthog-js v${{ needs.build-s3-artifacts.outputs.committed-version }} dist to S3! ' emoji_reaction: 'x' notify-released: name: Notify Slack - Released needs: [notify-approval-needed, publish, upload-s3] runs-on: ubuntu-latest - if: always() && needs.publish.result == 'success' && needs.notify-approval-needed.outputs.slack_ts != '' + if: always() && needs.publish.result == 'success' && needs.upload-s3.result == 'success' && needs.notify-approval-needed.outputs.slack_ts != '' steps: - name: Checkout repository uses: actions/checkout@v6 From 33ae2d838cf8fce2495f9f75fec212521b21fd5a Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Wed, 1 Apr 2026 16:45:01 -0400 Subject: [PATCH 4/9] fix: rename TMPDIR to TMPWORKDIR to avoid shadowing POSIX reserved env var --- .github/scripts/upload-posthog-js-s3.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/scripts/upload-posthog-js-s3.sh b/.github/scripts/upload-posthog-js-s3.sh index 8523cc72f1..7adae56915 100755 --- a/.github/scripts/upload-posthog-js-s3.sh +++ b/.github/scripts/upload-posthog-js-s3.sh @@ -36,27 +36,27 @@ aws s3 cp "$DIST_DIR/" "s3://$BUCKET/$VERSION/" \ --content-type "application/javascript" echo "==> Updating versions.json in s3://$BUCKET/" -TMPDIR="$(mktemp -d)" -trap 'rm -rf "$TMPDIR"' EXIT +TMPWORKDIR="$(mktemp -d)" +trap 'rm -rf "$TMPWORKDIR"' EXIT # Distinguish "file doesn't exist" from real errors (auth, network). # A blind fallback to '[]' on any error would silently drop all previous versions. -if aws s3 cp "s3://$BUCKET/versions.json" "$TMPDIR/versions.json"; then +if aws s3 cp "s3://$BUCKET/versions.json" "$TMPWORKDIR/versions.json"; then echo "Downloaded existing versions.json" elif aws s3api head-object --bucket "$BUCKET" --key "versions.json" 2>/dev/null; then echo "ERROR: versions.json exists but could not be downloaded" >&2 exit 1 else echo "No existing versions.json found, starting fresh" - echo '[]' > "$TMPDIR/versions.json" + echo '[]' > "$TMPWORKDIR/versions.json" fi -if jq -e --arg v "$VERSION" '.[] | select(.version == $v)' "$TMPDIR/versions.json" > /dev/null 2>&1; then +if jq -e --arg v "$VERSION" '.[] | select(.version == $v)' "$TMPWORKDIR/versions.json" > /dev/null 2>&1; then echo "Version $VERSION already in versions.json, skipping" else jq --arg v "$VERSION" --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ - '. + [{"version": $v, "timestamp": $ts}]' "$TMPDIR/versions.json" > "$TMPDIR/versions_updated.json" - aws s3 cp "$TMPDIR/versions_updated.json" "s3://$BUCKET/versions.json" \ + '. + [{"version": $v, "timestamp": $ts}]' "$TMPWORKDIR/versions.json" > "$TMPWORKDIR/versions_updated.json" + aws s3 cp "$TMPWORKDIR/versions_updated.json" "s3://$BUCKET/versions.json" \ --content-type "application/json" echo "Added v$VERSION to versions.json" fi From 41e40e9ca9430c065f462547a1ee3bcf2f36ce6a Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Wed, 1 Apr 2026 16:45:10 -0400 Subject: [PATCH 5/9] fix: validate versions.json structure before uploading to S3 --- .github/scripts/upload-posthog-js-s3.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/scripts/upload-posthog-js-s3.sh b/.github/scripts/upload-posthog-js-s3.sh index 7adae56915..d588c73f6c 100755 --- a/.github/scripts/upload-posthog-js-s3.sh +++ b/.github/scripts/upload-posthog-js-s3.sh @@ -56,6 +56,18 @@ if jq -e --arg v "$VERSION" '.[] | select(.version == $v)' "$TMPWORKDIR/versions else jq --arg v "$VERSION" --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ '. + [{"version": $v, "timestamp": $ts}]' "$TMPWORKDIR/versions.json" > "$TMPWORKDIR/versions_updated.json" + + # Validate the updated manifest before uploading: must be a non-empty JSON array + # where every entry has .version and .timestamp strings. + if ! jq -e 'if type != "array" then error + elif length == 0 then error + elif any(.[]; (.version | type) != "string" or (.timestamp | type) != "string") then error + else true end' "$TMPWORKDIR/versions_updated.json" > /dev/null 2>&1; then + echo "ERROR: versions_updated.json failed validation — aborting upload" >&2 + cat "$TMPWORKDIR/versions_updated.json" >&2 + exit 1 + fi + aws s3 cp "$TMPWORKDIR/versions_updated.json" "s3://$BUCKET/versions.json" \ --content-type "application/json" echo "Added v$VERSION to versions.json" From bbeadb3c2397d9452cea8cf706058c570cac765d Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Wed, 1 Apr 2026 16:45:26 -0400 Subject: [PATCH 6/9] fix: use floating major tag for check-package-version (@v2) --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d50025b09d..3cb36076ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -224,7 +224,7 @@ jobs: - name: Check ${{ matrix.package.name }} version and detect an update id: check-package-version - uses: PostHog/check-package-version@v2.1.0 + uses: PostHog/check-package-version@v2 with: path: ${{ steps.get-package-path.outputs.path }} @@ -355,7 +355,7 @@ jobs: - name: Check posthog-js version id: check-version - uses: PostHog/check-package-version@v2.1.0 + uses: PostHog/check-package-version@v2 with: path: packages/browser From fa44b4a9833d83a5a862a21db0d9213abf17274b Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Wed, 1 Apr 2026 16:45:33 -0400 Subject: [PATCH 7/9] fix: use fetch-depth 1 for build-s3-artifacts checkout (full history not needed) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3cb36076ec..3087b119dc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -351,7 +351,7 @@ jobs: uses: actions/checkout@v6 with: ref: ${{ needs.version-bump.outputs.commit-hash }} - fetch-depth: 0 + fetch-depth: 1 - name: Check posthog-js version id: check-version From a8c6f8976a0a0e74633b2d989f49d7896ea12e21 Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Wed, 1 Apr 2026 16:45:42 -0400 Subject: [PATCH 8/9] fix: allow notify-released when upload-s3 is skipped (non-posthog-js releases) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3087b119dc..9abf2af283 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -434,7 +434,7 @@ jobs: name: Notify Slack - Released needs: [notify-approval-needed, publish, upload-s3] runs-on: ubuntu-latest - if: always() && needs.publish.result == 'success' && needs.upload-s3.result == 'success' && needs.notify-approval-needed.outputs.slack_ts != '' + if: always() && needs.publish.result == 'success' && (needs.upload-s3.result == 'success' || needs.upload-s3.result == 'skipped') && needs.notify-approval-needed.outputs.slack_ts != '' steps: - name: Checkout repository uses: actions/checkout@v6 From 2e0d22ca9e7f20cbc75b007db2bf8b50e649ebe0 Mon Sep 17 00:00:00 2001 From: Dustin Byrne Date: Wed, 1 Apr 2026 16:48:32 -0400 Subject: [PATCH 9/9] fix: validate versions.json length is exactly original + 1 --- .github/scripts/upload-posthog-js-s3.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/scripts/upload-posthog-js-s3.sh b/.github/scripts/upload-posthog-js-s3.sh index d588c73f6c..230258e979 100755 --- a/.github/scripts/upload-posthog-js-s3.sh +++ b/.github/scripts/upload-posthog-js-s3.sh @@ -58,9 +58,10 @@ else '. + [{"version": $v, "timestamp": $ts}]' "$TMPWORKDIR/versions.json" > "$TMPWORKDIR/versions_updated.json" # Validate the updated manifest before uploading: must be a non-empty JSON array - # where every entry has .version and .timestamp strings. - if ! jq -e 'if type != "array" then error - elif length == 0 then error + # where every entry has .version and .timestamp strings, and length is exactly original + 1. + EXPECTED_LENGTH=$(( $(jq 'length' "$TMPWORKDIR/versions.json") + 1 )) + if ! jq -e --argjson expected "$EXPECTED_LENGTH" 'if type != "array" then error + elif length != $expected then error elif any(.[]; (.version | type) != "string" or (.timestamp | type) != "string") then error else true end' "$TMPWORKDIR/versions_updated.json" > /dev/null 2>&1; then echo "ERROR: versions_updated.json failed validation — aborting upload" >&2