Skip to content

Commit c421945

Browse files
committed
Add removed dependencies tracking and PR reporting
- Detect dependencies removed since last extraction - Show removed deps in console output (first 10) - Generate JSON report of removed dependencies - Include removed deps list in nightly PR body with details - Flag critical removed dependencies - Update baseline count dynamically Signed-off-by: Dan Gil <[email protected]>
1 parent 97bc8ef commit c421945

File tree

2 files changed

+95
-1
lines changed

2 files changed

+95
-1
lines changed

.github/workflows/dependency-extraction-nightly.yml

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ jobs:
5050
# Generate timestamped version (for artifacts)
5151
python3 .github/workflows/extract_dependency_versions.py \
5252
--output .github/reports/dependency_versions_${TIMESTAMP}.csv \
53-
--report-unversioned
53+
--report-unversioned \
54+
--report-removed .github/reports/removed_dependencies.json
5455
5556
# Copy to latest version (for repo tracking)
5657
mkdir -p .github/reports
@@ -74,9 +75,41 @@ jobs:
7475
changed_count=$(grep -c ",Changed," .github/reports/dependency_versions_latest.csv 2>/dev/null || echo "0")
7576
unchanged_count=$(grep -c ",Unchanged," .github/reports/dependency_versions_latest.csv 2>/dev/null || echo "0")
7677
78+
# Parse removed dependencies from JSON
79+
if [ -f ".github/reports/removed_dependencies.json" ]; then
80+
removed_count=$(python3 -c "import json; print(json.load(open('.github/reports/removed_dependencies.json'))['count'])" 2>/dev/null || echo "0")
81+
82+
# Format removed dependencies list for PR body (limit to first 10)
83+
removed_list=$(python3 -c "
84+
import json
85+
try:
86+
data = json.load(open('.github/reports/removed_dependencies.json'))
87+
removed = data['removed'][:10] # First 10
88+
lines = []
89+
for dep in removed:
90+
critical = ' **[CRITICAL]**' if dep.get('Critical') == 'Yes' else ''
91+
lines.append(f\" • **{dep['Dependency Name']}** (was: \`{dep['Version']}\`){critical}\")
92+
lines.append(f\" _from {dep['Source File']}_\")
93+
94+
if data['count'] > 10:
95+
lines.append(f\" _... and {data['count'] - 10} more (see CSV for full list)_\")
96+
97+
print('\n'.join(lines))
98+
except:
99+
print(' _No removed dependencies_')
100+
" 2>/dev/null || echo " _Unable to parse removed dependencies_")
101+
else
102+
removed_count="0"
103+
removed_list=" _No removed dependencies_"
104+
fi
105+
77106
echo "new_deps=$new_count" >> $GITHUB_OUTPUT
78107
echo "changed_deps=$changed_count" >> $GITHUB_OUTPUT
79108
echo "unchanged_deps=$unchanged_count" >> $GITHUB_OUTPUT
109+
echo "removed_deps=$removed_count" >> $GITHUB_OUTPUT
110+
echo "removed_list<<EOF" >> $GITHUB_OUTPUT
111+
echo "$removed_list" >> $GITHUB_OUTPUT
112+
echo "EOF" >> $GITHUB_OUTPUT
80113
else
81114
echo "has_changes=false" >> $GITHUB_OUTPUT
82115
fi
@@ -96,8 +129,12 @@ jobs:
96129
### 📊 Summary
97130
- **New Dependencies:** ${{ steps.check_changes.outputs.new_deps }}
98131
- **Changed Versions:** ${{ steps.check_changes.outputs.changed_deps }}
132+
- **Removed Dependencies:** ${{ steps.check_changes.outputs.removed_deps }}
99133
- **Unchanged:** ${{ steps.check_changes.outputs.unchanged_deps }}
100134
135+
### 🗑️ Removed Dependencies
136+
${{ steps.check_changes.outputs.removed_list }}
137+
101138
### 📋 Files Updated
102139
- ✅ `.github/reports/dependency_versions_latest.csv` - Latest dependency snapshot
103140
- ✅ `.github/reports/unversioned_dependencies_latest.csv` - Unversioned deps report (if applicable)
@@ -107,6 +144,7 @@ jobs:
107144
### ✔️ Review Checklist
108145
- [ ] Review new dependencies for security/licensing concerns
109146
- [ ] Check version changes for breaking updates
147+
- [ ] Review removed dependencies (intentional?)
110148
- [ ] Verify unversioned dependencies report
111149
- [ ] Update baseline count if increase is expected
112150

.github/workflows/extract_dependency_versions.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,15 +1407,55 @@ def sort_key(dep):
14071407
new_count = sum(1 for d in self.dependencies if d['Status'] == 'New')
14081408
changed_count = sum(1 for d in self.dependencies if d['Status'] == 'Changed')
14091409
unchanged_count = sum(1 for d in self.dependencies if d['Status'] == 'Unchanged')
1410+
removed = self.get_removed_dependencies()
14101411

14111412
print(f"✓ Written {len(self.dependencies)} dependencies to {output_path}")
14121413
print(f" Changes since previous version:")
14131414
print(f" New: {new_count}")
14141415
print(f" Changed: {changed_count}")
1416+
print(f" Removed: {len(removed)}")
14151417
print(f" Unchanged: {unchanged_count}")
1418+
1419+
if removed:
1420+
print(f"\n Removed dependencies:")
1421+
for dep in removed[:10]: # Show first 10
1422+
critical_flag = " [CRITICAL]" if dep['Critical'] == 'Yes' else ""
1423+
print(f" • {dep['Dependency Name']} (was: {dep['Version']}){critical_flag}")
1424+
print(f" from {dep['Source File']}")
1425+
if len(removed) > 10:
1426+
print(f" ... and {len(removed) - 10} more")
14161427
else:
14171428
print(f"✓ Written {len(self.dependencies)} dependencies to {output_path}")
14181429

1430+
def get_removed_dependencies(self) -> List[Dict[str, str]]:
1431+
"""
1432+
Detect dependencies that were in the previous CSV but not in the current extraction.
1433+
Returns list of removed dependencies with their previous information.
1434+
"""
1435+
if not self.previous_latest_dependencies:
1436+
return []
1437+
1438+
# Build set of current dependency keys
1439+
current_keys = set()
1440+
for dep in self.dependencies:
1441+
key = f"{dep['Component']}:{dep['Category']}:{dep['Dependency Name']}"
1442+
current_keys.add(key)
1443+
1444+
# Find dependencies in previous but not in current
1445+
removed = []
1446+
for prev_key, prev_dep in self.previous_latest_dependencies.items():
1447+
if prev_key not in current_keys:
1448+
removed.append({
1449+
'Component': prev_dep.get('Component', ''),
1450+
'Category': prev_dep.get('Category', ''),
1451+
'Dependency Name': prev_dep.get('Dependency Name', ''),
1452+
'Version': prev_dep.get('Version', ''),
1453+
'Source File': prev_dep.get('Source File', ''),
1454+
'Critical': prev_dep.get('Critical', 'No')
1455+
})
1456+
1457+
return removed
1458+
14191459
def write_unversioned_report(self, output_path: Path) -> None:
14201460
"""Write a separate report of unversioned dependencies."""
14211461
unversioned = [
@@ -1591,6 +1631,11 @@ def main():
15911631
action="store_true",
15921632
help="Generate separate report of unversioned dependencies"
15931633
)
1634+
parser.add_argument(
1635+
"--report-removed",
1636+
type=str,
1637+
help="Output removed dependencies to JSON file (e.g., removed.json)"
1638+
)
15941639
parser.add_argument(
15951640
"--config",
15961641
type=Path,
@@ -1752,6 +1797,17 @@ def main():
17521797
unversioned_path = output_path.parent / f"{output_path.stem}_unversioned{output_path.suffix}"
17531798
extractor.write_unversioned_report(unversioned_path)
17541799

1800+
# Write removed dependencies report if requested
1801+
if args.report_removed:
1802+
removed_deps = extractor.get_removed_dependencies()
1803+
removed_path = Path(args.report_removed)
1804+
with open(removed_path, 'w') as f:
1805+
json.dump({
1806+
'count': len(removed_deps),
1807+
'removed': removed_deps
1808+
}, f, indent=2)
1809+
print(f"✓ Written {len(removed_deps)} removed dependencies to {removed_path}")
1810+
17551811
# Print summary
17561812
extractor.print_summary()
17571813

0 commit comments

Comments
 (0)