Skip to content

Commit 94e7aa8

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

File tree

1 file changed

+96
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)