diff --git a/.github/workflows/ci-python.yml b/.github/workflows/ci-python.yml index a098fd9f6ef1..727c691fa2ba 100644 --- a/.github/workflows/ci-python.yml +++ b/.github/workflows/ci-python.yml @@ -74,6 +74,8 @@ jobs: - 'frontend/src/queries/schema.json' # Products changes need to trigger Python CI to validate the json usage is still valid - 'frontend/src/products.json' + # A hand-edit to the generated taxonomy JSON must reach the drift check + - 'frontend/src/taxonomy/core-filter-definitions-by-group.json' # hogli CLI changes need validation - 'tools/hogli/**' - 'tools/hogli-commands/hogli_commands/**' @@ -194,6 +196,16 @@ jobs: run: | npm run schema:build:python && git diff --exit-code + # Calls the generator directly because this job installs neither pnpm nor + # node_modules. Keep the step after "Install Python dependencies": importing + # posthog.taxonomy.taxonomy pulls in Django via posthog/__init__.py, so the + # generator needs the full synced dependency set, not just stdlib. + - name: Check if the taxonomy JSON is up to date + shell: bash + run: | + python3 bin/build-taxonomy-json.py + git diff --exit-code -- frontend/src/taxonomy/core-filter-definitions-by-group.json + # Warn-only while the pre-existing violation backlog settles: findings annotate # the diff and land in the shared CI report comment, never a job failure. # Scoped to the PR's own diff because C901 stays off repo-wide. Skip on @@ -303,12 +315,6 @@ jobs: name: junit-results-dependency-detection path: junit-dependency-detection.xml - # - name: Check if "taxonomy.json/taxonomy.tsx" is up to date - # if: needs.changes.outputs.python == 'true' - # shell: bash - # run: | - # npm run taxonomy:build:json && git diff --exit-code - # Collate job - single required status check for branch protection. # The code-quality job skips when no Python changes, but this job always # runs and reports success, allowing PRs to merge. diff --git a/bin/build-taxonomy-json.py b/bin/build-taxonomy-json.py index 21d3d64dc828..09dda082da7e 100644 --- a/bin/build-taxonomy-json.py +++ b/bin/build-taxonomy-json.py @@ -1,7 +1,6 @@ import os import sys import json -import subprocess # Add the project root to Python path sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -18,6 +17,7 @@ } json.dump(with_comment, json_file, indent=4, sort_keys=True) -subprocess.run( - ["pnpm", "exec", "oxfmt", filename], -) +# Do not add a formatter pass here. json.dump is the only writer, so the output stays +# byte-stable and the taxonomy drift check in ci-python.yml cannot flap. The other half +# of that guarantee is the ignorePatterns entry in .oxfmtrc.json, which keeps +# lint-staged's `bin/hogli format:yaml` (oxfmt) off this file. Keep both. diff --git a/frontend/src/taxonomy/coreFilterDefinitions.test.ts b/frontend/src/taxonomy/coreFilterDefinitions.test.ts new file mode 100644 index 000000000000..3be32bdca203 --- /dev/null +++ b/frontend/src/taxonomy/coreFilterDefinitions.test.ts @@ -0,0 +1,22 @@ +import * as fs from 'fs' +import * as path from 'path' + +const REPO_ROOT = path.resolve(__dirname, '../../..') +const GENERATED_JSON = 'frontend/src/taxonomy/core-filter-definitions-by-group.json' + +describe('core filter definitions JSON', () => { + test('stays in the oxfmt ignore list, so the Python generator owns its bytes', () => { + const config = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, '.oxfmtrc.json'), 'utf-8')) + const ignorePatterns: string[] = config.ignorePatterns ?? [] + + const missingEntryHint = ignorePatterns.includes(GENERATED_JSON) + ? undefined + : `${GENERATED_JSON} is missing from ignorePatterns in .oxfmtrc.json. lint-staged pipes staged JSON ` + + 'through `bin/hogli format:yaml` (oxfmt), and that entry is the only thing keeping oxfmt off ' + + 'this file. Without it the committed bytes stop matching the json.dump output of ' + + 'bin/build-taxonomy-json.py, so the taxonomy drift check in ci-python.yml fails for whoever ' + + 'next edits posthog/taxonomy/taxonomy.py. Restore the entry rather than reformatting the JSON.' + + expect(missingEntryHint).toBeUndefined() + }) +})