Skip to content

Commit 94121ab

Browse files
committed
Experiment with finding feature flag references in the code
1 parent 41986fb commit 94121ab

1 file changed

Lines changed: 93 additions & 0 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
name: 'PoC: GitHub Code References'
2+
3+
on:
4+
schedule:
5+
- cron: '0 0 * * *' # Runs daily at midnight UTC
6+
workflow_dispatch:
7+
pull_request: # DROPME
8+
9+
env:
10+
FLAGSMITH_PUBLIC_KEY: ENktaJnfLVbLifybz34JmX
11+
FLAGSMITH_EDGE_API_URL: https://edge.api.flagsmith.com
12+
PYTHON_VERSION: '3.13'
13+
PYTHON_REQUESTS_VERSION: '2.32.4'
14+
15+
jobs:
16+
collect-code-references:
17+
runs-on: depot-ubuntu-latest
18+
steps:
19+
- name: Checkout code
20+
uses: actions/checkout@v4
21+
22+
- uses: actions/setup-python@v5
23+
with:
24+
python-version: ${{ env.PYTHON_VERSION }}
25+
26+
- name: Install dependencies
27+
run: |
28+
pip install requests==${{ env.PYTHON_REQUESTS_VERSION }}
29+
30+
- name: Collect code references
31+
id: collect
32+
shell: python
33+
run: |
34+
import os
35+
import re
36+
from collections import deque
37+
from pathlib import Path
38+
from typing import Generator
39+
40+
import requests
41+
42+
EXCLUDE_PATTERNS = ["node_modules", "venv", ".git", "cache", "build", "htmlcov", "docs", ".json"]
43+
44+
def is_file_binary(file_path: Path) -> bool:
45+
"""Check if a file is binary."""
46+
with file_path.open("rb") as file:
47+
chunk = file.read(1024)
48+
return b'\0' in chunk
49+
50+
51+
def find_references(feature_names: list[str]) -> Generator[tuple[str, Path, int], None, None]:
52+
"""Search for references to a feature name in the codebase."""
53+
all_files = Path('.').glob("**/*")
54+
for path in all_files:
55+
if any(pattern in str(path).lower() for pattern in EXCLUDE_PATTERNS):
56+
continue
57+
if not path.is_file():
58+
continue
59+
if is_file_binary(path):
60+
continue
61+
context: deque[str] = deque(maxlen=2)
62+
with path.open("r", encoding="utf-8", errors="ignore") as file:
63+
for line_number, line in enumerate(file, start=1):
64+
context.append(line)
65+
for feature_name in feature_names:
66+
if feature_name not in line: # Match feature name
67+
continue
68+
if (
69+
re.search(fr"""[A-Z][A-Z0-9_]{3,}\s*=\s*(['"]){feature_name}\1""", line) or # Constant assignment
70+
re.search(fr"""(?ism:(?:feature|flag)[^\(]*\(.*(["']){feature_name})\1""", "".join(context)) # Function call
71+
):
72+
yield feature_name, path, line_number
73+
74+
75+
# Fetch visible features
76+
all_flags = requests.get(f"${{ env.FLAGSMITH_EDGE_API_URL }}/api/v1/flags", headers={"X-Environment-Key": "${{ env.FLAGSMITH_PUBLIC_KEY }}"}).json()
77+
feature_names = sorted([flag["feature"]["name"] for flag in all_flags])
78+
print("Feature names:", feature_names)
79+
80+
# Find code references
81+
code_references = [
82+
(feature_name, file_path, line_number)
83+
for feature_name, file_path, line_number in find_references(feature_names)
84+
]
85+
86+
# Output to GHA
87+
with open(os.environ["GITHUB_OUTPUT"], "a") as gh_output:
88+
print(json.dumps({"code_references": code_references}), file=gh_output)
89+
90+
- name: Display code references
91+
run: |
92+
echo "Code References:"
93+
echo "${{ steps.collect.outputs.code_references }}"

0 commit comments

Comments
 (0)