Skip to content

Commit 712283a

Browse files
authored
Add PR validation workflow for feature flag config files (#509)
1 parent 06c38e7 commit 712283a

1 file changed

Lines changed: 150 additions & 0 deletions

File tree

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
name: Validate Feature Flags
2+
run-name: Validate Feature Flags
3+
4+
on:
5+
pull_request:
6+
branches: [main]
7+
paths:
8+
- 'assets/featureFlag/*.json'
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
validate:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v5
18+
19+
- name: Validate JSON syntax
20+
run: |
21+
FAILED=0
22+
for file in assets/featureFlag/*.json; do
23+
if ! jq empty "$file" 2>/dev/null; then
24+
echo "ERROR: $file is not valid JSON"
25+
FAILED=1
26+
fi
27+
done
28+
if [[ "$FAILED" -eq 1 ]]; then exit 1; fi
29+
echo "All feature flag files are valid JSON"
30+
31+
- name: Validate schema structure
32+
run: |
33+
FAILED=0
34+
for file in assets/featureFlag/*.json; do
35+
# Required top-level fields
36+
for field in version description features; do
37+
if ! jq -e "has(\"$field\")" "$file" > /dev/null 2>&1; then
38+
echo "ERROR: $file: missing required field '$field'"
39+
FAILED=1
40+
fi
41+
done
42+
43+
# version must be a number
44+
if ! jq -e '.version | type == "number"' "$file" > /dev/null 2>&1; then
45+
echo "ERROR: $file: 'version' must be a number"
46+
FAILED=1
47+
fi
48+
49+
# description must be a string
50+
if ! jq -e '.description | type == "string"' "$file" > /dev/null 2>&1; then
51+
echo "ERROR: $file: 'description' must be a string"
52+
FAILED=1
53+
fi
54+
55+
# features must be an object
56+
if ! jq -e '.features | type == "object"' "$file" > /dev/null 2>&1; then
57+
echo "ERROR: $file: 'features' must be an object"
58+
FAILED=1
59+
fi
60+
61+
# No extra top-level keys
62+
EXTRA_KEYS=$(jq -r 'keys[] | select(. != "version" and . != "description" and . != "features")' "$file" || true)
63+
if [[ -n "$EXTRA_KEYS" ]]; then
64+
echo "ERROR: $file: unexpected top-level keys: $EXTRA_KEYS"
65+
FAILED=1
66+
fi
67+
68+
# Validate each feature entry
69+
for feature in $(jq -r '.features | keys[]' "$file"); do
70+
# 'enabled' is required and must be boolean
71+
if ! jq -e ".features[\"$feature\"].enabled | type == \"boolean\"" "$file" > /dev/null 2>&1; then
72+
echo "ERROR: $file: feature '$feature' must have a boolean 'enabled' field"
73+
FAILED=1
74+
fi
75+
76+
# 'fleetPercentage' if present must be a number between 0 and 100
77+
HAS_FLEET=$(jq -r ".features[\"$feature\"] | has(\"fleetPercentage\")" "$file" || true)
78+
if [[ "$HAS_FLEET" == "true" ]]; then
79+
VALID_FLEET=$(jq -r ".features[\"$feature\"].fleetPercentage | type == \"number\" and . >= 0 and . <= 100" "$file" || true)
80+
if [[ "$VALID_FLEET" != "true" ]]; then
81+
echo "ERROR: $file: feature '$feature' fleetPercentage must be a number between 0 and 100"
82+
FAILED=1
83+
fi
84+
fi
85+
86+
# 'allowlistedRegions' if present must be an array of strings
87+
HAS_REGIONS=$(jq -r ".features[\"$feature\"] | has(\"allowlistedRegions\")" "$file" || true)
88+
if [[ "$HAS_REGIONS" == "true" ]]; then
89+
VALID_REGIONS=$(jq -r ".features[\"$feature\"].allowlistedRegions | type == \"array\" and all(type == \"string\")" "$file" || true)
90+
if [[ "$VALID_REGIONS" != "true" ]]; then
91+
echo "ERROR: $file: feature '$feature' allowlistedRegions must be an array of strings"
92+
FAILED=1
93+
fi
94+
fi
95+
96+
# No unexpected keys in feature entry
97+
EXTRA_FEATURE_KEYS=$(jq -r ".features[\"$feature\"] | keys[] | select(. != \"enabled\" and . != \"fleetPercentage\" and . != \"allowlistedRegions\")" "$file" || true)
98+
if [[ -n "$EXTRA_FEATURE_KEYS" ]]; then
99+
echo "ERROR: $file: feature '$feature' has unexpected keys: $EXTRA_FEATURE_KEYS"
100+
FAILED=1
101+
fi
102+
done
103+
done
104+
105+
if [[ "$FAILED" -eq 1 ]]; then exit 1; fi
106+
echo "All feature flag files pass schema validation"
107+
108+
- name: Validate cross-environment consistency
109+
run: |
110+
FAILED=0
111+
FILES=(assets/featureFlag/*.json)
112+
113+
if [[ ${#FILES[@]} -lt 2 ]]; then
114+
echo "Only one environment file found, skipping cross-env check"
115+
exit 0
116+
fi
117+
118+
# All environment files must define the same set of feature keys
119+
REFERENCE_FILE="${FILES[0]}"
120+
REFERENCE_KEYS=$(jq -r '.features | keys | sort | join(",")' "$REFERENCE_FILE")
121+
122+
for file in "${FILES[@]:1}"; do
123+
FILE_KEYS=$(jq -r '.features | keys | sort | join(",")' "$file")
124+
if [[ "$REFERENCE_KEYS" != "$FILE_KEYS" ]]; then
125+
echo "ERROR: Feature key mismatch: $(basename "$REFERENCE_FILE") has [$REFERENCE_KEYS] but $(basename "$file") has [$FILE_KEYS]"
126+
FAILED=1
127+
fi
128+
done
129+
130+
if [[ "$FAILED" -eq 1 ]]; then exit 1; fi
131+
echo "All environments define the same feature flags"
132+
133+
- name: Validate registered feature flags exist
134+
run: |
135+
# These are the feature flag keys registered in FeatureFlagSupplier.ts.
136+
# Update this list when adding or removing feature flags from the codebase.
137+
REGISTERED_FLAGS="Constants EnhancedDryRun"
138+
FAILED=0
139+
140+
for file in assets/featureFlag/*.json; do
141+
for flag in $REGISTERED_FLAGS; do
142+
if ! jq -e ".features | has(\"$flag\")" "$file" > /dev/null 2>&1; then
143+
echo "ERROR: $file: missing registered feature flag '$flag'"
144+
FAILED=1
145+
fi
146+
done
147+
done
148+
149+
if [[ "$FAILED" -eq 1 ]]; then exit 1; fi
150+
echo "All registered feature flags are present in every environment"

0 commit comments

Comments
 (0)