Skip to content

feat(support): add AI gateway target area to support form (#62587) #307096

feat(support): add AI gateway target area to support form (#62587)

feat(support): add AI gateway target area to support form (#62587) #307096

Workflow file for this run

name: Frontend CI
on:
pull_request:
merge_group:
push:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
pull-requests: write
actions: read
jobs:
# Job to decide if we should run frontend ci
# See https://github.com/dorny/paths-filter#conditional-execution for more details
# we skip each step individually, so they are still reported as success
# because many of them are required for CI checks to be green
changes:
runs-on: ubuntu-latest
timeout-minutes: 5
name: Determine need to run frontend checks
outputs:
frontend: ${{ steps.filter.outputs.frontend || 'true' }}
frontend_code: ${{ steps.filter.outputs.frontend_code || 'true' }}
steps:
# For pull requests it's not necessary to check out the code, but we
# also want this to run on master, so we need to check out
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
id: app-token
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
with:
client-id: ${{ secrets.GH_APP_POSTHOG_PATHS_FILTER_APP_ID }}
private-key: ${{ secrets.GH_APP_POSTHOG_PATHS_FILTER_PRIVATE_KEY }}
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: filter
if: github.event_name != 'push' # Run all tests on master push
with:
token: ${{ steps.app-token.outputs.token || github.token }}
filters: |
frontend:
# Avoid running frontend tests for irrelevant changes
# NOTE: we are at risk of missing a dependency here.
- 'bin/**'
- 'frontend/**'
- 'ee/frontend/**'
- 'common/esbuilder/**'
- 'products/**/*.ts'
- 'products/**/*.tsx'
- 'products/**/*.json'
- 'playwright/**'
- 'services/mcp/**'
- 'docs/**/*.tsx'
# Make sure we run if someone is explicitly change the workflow
- .github/workflows/ci-frontend.yml
# various JS config files
- .oxlintrc.json
- .oxfmtrc*
- babel.config.js
- package.json
- pnpm-lock.yaml
- jest.*.ts
- tsconfig.json
- tsconfig.*.json
- webpack.config.js
- stylelint*
- '**/*.md'
- '**/*.mdx'
- '**/*.yaml'
- '**/*.yml'
- .config/.markdownlint-cli2.jsonc
# Like `frontend` but excludes doc/config-only files that don't affect
# compiled output — used to skip Jest, bundle-size, and TypeScript checks
# when only markdown or YAML files change.
frontend_code:
- 'bin/**'
- 'frontend/**'
- 'ee/frontend/**'
- 'common/esbuilder/**'
- 'products/**/*.ts'
- 'products/**/*.tsx'
- 'products/**/*.json'
- 'playwright/**'
- 'services/mcp/**'
- .github/workflows/ci-frontend.yml
- .oxlintrc.json
- .oxfmtrc*
- babel.config.js
- package.json
- pnpm-lock.yaml
- jest.*.ts
- tsconfig.json
- tsconfig.*.json
- webpack.config.js
- stylelint*
frontend-format:
name: Frontend formatting
needs: changes
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install pnpm
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: .nvmrc
token: ${{ github.token }}
- name: Get pnpm store path
id: pnpm-store
run: echo "path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Restore pnpm cache
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-
- name: Install package.json dependencies with pnpm
run: |
pnpm --filter=@posthog/playwright... install --frozen-lockfile
bin/turbo --filter=@posthog/frontend prepare
- name: Save pnpm cache
if: github.ref == 'refs/heads/master'
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
- name: Check formatting with oxfmt
run: pnpm run format:frontend:check
- name: Lint with Stylelint
run: pnpm run lint:css
- name: Lint with Oxlint
run: pnpm exec oxlint --quiet
- name: Check markdown formatting with oxfmt
run: pnpm exec oxfmt --check "**/*.{md,mdx}"
- name: Check YAML formatting with oxfmt
run: pnpm exec oxfmt --check "**/*.{yaml,yml}"
- name: Lint markdown files
run: pnpm exec markdownlint-cli2 --config .config/.markdownlint-cli2.jsonc "**/*.{md,mdx}"
frontend-bundle-size:
name: Frontend bundle size
needs: [changes]
if: needs.changes.outputs.frontend_code == 'true' && github.event_name != 'merge_group'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
filter: blob:none
- name: Install pnpm
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: .nvmrc
token: ${{ github.token }}
- name: Get pnpm store path
id: pnpm-store
run: echo "path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Restore pnpm cache
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-
- name: Install package.json dependencies with pnpm
run: |
pnpm --filter=@posthog/playwright... install --frozen-lockfile
bin/turbo --filter=@posthog/frontend prepare
- name: Save pnpm cache
if: github.ref == 'refs/heads/master'
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
- name: Build products
run: pnpm --filter=@posthog/frontend build:products
- name: Check frontend bundle size
uses: preactjs/compressed-size-action@66325aad6443cb7cf89c4bfcd414aea2367cda94 # v2.9.1
with:
build-script: '--filter=@posthog/frontend build:with-report'
install-script: 'pnpm --filter=@posthog/frontend... install'
compression: 'none'
# dist-report/ carries the source bundle (posthog-app, exporter, …) and source
# path in the filename, so strip-hash yields a deterministic identity. dist/ has
# cross-bundle basename collisions that cause phantom size changes.
pattern: 'frontend/dist-report/**/*.js'
# Shared chunk hashes change every build, ignore those
exclude: '{**/_chunks/chunk*.js}'
# Only show large changes
minimum-change-threshold: 1000
order-by: 'Size:desc'
strip-hash: "-[A-Za-z0-9]{8}\\.js$"
# The eager graph check runs in --report-only mode inside build:with-report
# (compressed-size-action runs that for the PR AND base builds — a failing
# base build must not abort the action for every open PR). These steps post
# the sticky PR comment and enforce the budgets from the PR build's report,
# which carries the checkout sha in its filename because the base build
# overwrites the plain one. After compressed-size-action the tree is on the
# BASE branch, so guard for the scripts' absence (also covers branches
# predating the check).
- name: Post eager graph comment
if: always() && github.event_name == 'pull_request'
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
if [ -f frontend/bin/post-eager-graph-comment.mjs ]; then
node frontend/bin/post-eager-graph-comment.mjs
else
echo "Skipping eager graph comment — script not present on this checkout"
fi
- name: Enforce eager graph budgets
if: always() && github.event_name == 'pull_request'
run: |
if [ ! -f frontend/bin/check-eager-graph.mjs ]; then
echo "Skipping eager graph enforcement — script not present on this checkout"
exit 0
fi
REPORT="frontend/eager-graph-report-${GITHUB_SHA}.json"
if [ ! -f "$REPORT" ]; then
REPORT="frontend/eager-graph-report.json"
fi
if [ ! -f "$REPORT" ]; then
echo "Skipping eager graph enforcement — no report found (branch may predate the check)"
exit 0
fi
node frontend/bin/check-eager-graph.mjs --assert-report "$REPORT"
- name: Check toolbar for CSP eval violations
run: pnpm --filter=@posthog/frontend check-toolbar-csp-eval
- name: Calculate dist folder size
id: dist-size
run: |
size_bytes=$(du -sb frontend/dist | cut -f1)
echo "size_bytes=${size_bytes}" >> $GITHUB_OUTPUT
echo "Frontend dist folder size: ${size_bytes} bytes ($(numfmt --to=iec-i --suffix=B ${size_bytes}))"
- name: Capture bundle size to PostHog
if: |
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'PostHog/posthog') ||
(github.event_name != 'pull_request' && github.repository == 'PostHog/posthog')
uses: PostHog/posthog-github-action@58dea254b598fb5d469c0699c98af8288a7f7650 # v1.2.0
with:
posthog-token: ${{secrets.POSTHOG_API_TOKEN}}
event: 'posthog-ci-frontend-bundle-size'
properties: |
{
"size_bytes": ${{ steps.dist-size.outputs.size_bytes }},
"branch": ${{ toJSON(github.head_ref || github.ref_name) }},
"sha": ${{ toJSON(github.sha) }},
"pr_number": ${{ github.event.pull_request.number || 'null' }},
"event_type": ${{ toJSON(github.event_name) }}
}
- name: Capture bundle size to DevEx PostHog
if: |
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'PostHog/posthog') ||
(github.event_name != 'pull_request' && github.repository == 'PostHog/posthog')
continue-on-error: true
uses: PostHog/posthog-github-action@58dea254b598fb5d469c0699c98af8288a7f7650 # v1.2.0
with:
posthog-token: ${{ secrets.POSTHOG_DEVEX_PROJECT_API_TOKEN }}
event: 'posthog-ci-frontend-bundle-size'
properties: |
{
"size_bytes": ${{ steps.dist-size.outputs.size_bytes }},
"branch": ${{ toJSON(github.head_ref || github.ref_name) }},
"sha": ${{ toJSON(github.sha) }},
"pr_number": ${{ github.event.pull_request.number || 'null' }},
"event_type": ${{ toJSON(github.event_name) }}
}
frontend-typescript-checks:
name: Frontend typechecking
needs: [changes]
if: needs.changes.outputs.frontend_code == 'true'
runs-on: ubuntu-latest
# Kea typegen invalidates on changes to any logic plus everything that depends on it.
# For changes to shared core (TaxonomicFilter, Insight, etc.) the dependent set is large
# and the cache miss costs ~5min on top of the master baseline (~10min). 15 is too tight.
timeout-minutes: 20
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install pnpm
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: .nvmrc
token: ${{ github.token }}
- name: Get pnpm store path
id: pnpm-store
run: echo "path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Restore pnpm cache
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-
- name: Install package.json dependencies with pnpm
run: |
pnpm --filter=@posthog/playwright... install --no-frozen-lockfile
bin/turbo --filter=@posthog/frontend prepare
- name: Save pnpm cache
if: github.ref == 'refs/heads/master'
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
- name: Restore .typegen cache
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: .typegen
key: ${{ runner.os }}-typegen-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: ${{ runner.os }}-typegen-
- name: Build products
run: pnpm --filter=@posthog/frontend build:products
- name: Check if products.tsx and products.json are up to date
run: pnpm --filter=@posthog/frontend build:products && git diff --exit-code
- name: Kea typegen
run: pnpm --filter=@posthog/frontend typegen:write
- name: Re-format files after typegen
run: pnpm exec oxfmt "./{products,frontend/src}/**/*.{ts,tsx}"
- name: Save .typegen cache
if: github.ref == 'refs/heads/master'
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: .typegen
key: ${{ runner.os }}-typegen-${{ hashFiles('pnpm-lock.yaml') }}
- name: Run typescript with strict
run: pnpm --filter=@posthog/frontend typescript:check
env:
NODE_OPTIONS: --max-old-space-size=16384
- name: Run tsgo (shadow, non-blocking)
run: pnpm --filter=@posthog/frontend typescript:check:tsgo
continue-on-error: true
- name: Check if schema.json and validators.js are up to date
run: pnpm --filter=@posthog/frontend schema:build:json && git diff --exit-code
- name: Check if mobile replay "schema.json" is up to date
run: pnpm --filter=@posthog/frontend mobile-replay:schema:build:json && git diff --exit-code
env:
NODE_OPTIONS: --max-old-space-size=8192
- name: Validate SetupTaskId completions
run: node bin/validate-setup-tasks.mjs
jest:
runs-on: ubuntu-latest
timeout-minutes: 15
needs: changes
if: needs.changes.outputs.frontend_code == 'true'
name: Jest test (${{ matrix.segment }} - ${{ matrix.chunk }})
strategy:
# If one test fails, still run the others
fail-fast: false
matrix:
segment: ['FOSS', 'EE']
chunk: [1, 2]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Remove ee
if: matrix.segment == 'FOSS'
run: rm -rf ee
- name: Install pnpm
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- name: Set up Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: .nvmrc
token: ${{ github.token }}
- name: Get pnpm store path
id: pnpm-store
run: echo "path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Restore pnpm cache
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-
- name: Install package.json dependencies with pnpm
run: pnpm --filter=@posthog/frontend... install --frozen-lockfile
- name: Save pnpm cache
if: github.ref == 'refs/heads/master'
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: node-cache-${{ runner.os }}-${{ runner.arch }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
- name: Test with Jest
run: bin/turbo run test --filter=@posthog/frontend -- --maxWorkers=2
env:
NODE_OPTIONS: --max-old-space-size=16384
SHARD_INDEX: ${{ matrix.chunk }}
SHARD_COUNT: 2
calculate-running-time:
name: Calculate running time
needs: [jest, frontend-typescript-checks, frontend-format, frontend-bundle-size, frontend_tests, changes]
runs-on: ubuntu-latest
timeout-minutes: 5
if: # Run on pull requests to PostHog/posthog + on PostHog/posthog outside of PRs - but never on forks or Dependabot (no secrets access)
always() && github.actor != 'dependabot[bot]' &&
needs.changes.outputs.frontend_code == 'true' && (
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'PostHog/posthog') ||
(github.event_name != 'pull_request' && github.repository == 'PostHog/posthog'))
steps:
- name: Get telemetry app token
id: telemetry-app-token
if: github.run_attempt == '1'
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with:
client-id: ${{ secrets.GH_APP_TELEMETRY_APP_ID }}
private-key: ${{ secrets.GH_APP_TELEMETRY_PRIVATE_KEY }}
- name: Capture running time to PostHog
if: github.run_attempt == '1'
uses: PostHog/posthog-github-action@58dea254b598fb5d469c0699c98af8288a7f7650 # v1.2.0
with:
posthog-token: ${{ secrets.POSTHOG_API_TOKEN }}
event: 'posthog-ci-running-time'
capture-run-duration: true
capture-job-durations: true
github-token: ${{ steps.telemetry-app-token.outputs.token }}
status-job: 'Frontend Tests Pass'
runner: 'github'
- name: Capture running time to DevEx PostHog
if: github.run_attempt == '1'
continue-on-error: true
uses: PostHog/posthog-github-action@58dea254b598fb5d469c0699c98af8288a7f7650 # v1.2.0
with:
posthog-token: ${{ secrets.POSTHOG_DEVEX_PROJECT_API_TOKEN }}
event: 'posthog-ci-running-time'
capture-run-duration: true
capture-job-durations: true
github-token: ${{ steps.telemetry-app-token.outputs.token }}
status-job: 'Frontend Tests Pass'
runner: 'github'
frontend_tests:
needs: [jest, frontend-format, frontend-bundle-size, frontend-typescript-checks]
name: Frontend Tests Pass
runs-on: ubuntu-latest
timeout-minutes: 5
if: always()
steps:
- run: exit 0
- name: Check outcomes
run: |
if [[ "${{ needs.jest.result }}" != "success" && "${{ needs.jest.result }}" != "skipped" ]]; then
echo "Frontend jest tests failed."
exit 1
fi
echo "Frontend jest tests passed."
if [[ "${{ needs.frontend-format.result }}" != "success" && "${{ needs.frontend-format.result }}" != "skipped" ]]; then
echo "Frontend linting failed."
exit 1
fi
echo "Frontend linting passed."
if [[ "${{ needs.frontend-bundle-size.result }}" != "success" && "${{ needs.frontend-bundle-size.result }}" != "skipped" ]]; then
echo "Frontend bundle size checks failed."
exit 1
fi
echo "Frontend bundle size checks passed."
if [[ "${{ needs.frontend-typescript-checks.result }}" != "success" && "${{ needs.frontend-typescript-checks.result }}" != "skipped" ]]; then
echo "Frontend TypeScript checks failed."
exit 1
fi
echo "Frontend TypeScript checks passed."