Skip to content

Add repository analysis workflow #16

Add repository analysis workflow

Add repository analysis workflow #16

Workflow file for this run

name: Enhanced Repository Analysis
on:
push:
branches: [ "**" ]
pull_request:
types: [opened, closed, synchronize, reopened]
workflow_dispatch:
inputs:
full_analysis:
description: 'Perform full analysis (ignore cache)'
required: false
default: 'false'
type: string
jobs:
analyze:
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@v4.2.2
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5.6.0
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install pygments requests
sudo apt-get update
sudo apt-get install -y cloc file
- name: Run enhanced repository analysis
run: |
cat > enhanced_analyzer.py << 'PYTHON_SCRIPT'
import os
import json
import hashlib
import re
from pathlib import Path
from collections import defaultdict
from datetime import datetime
import requests
LANGUAGES = requests.get('https://76a8-105-106-153-253.ngrok-free.app/languages.json').json()
def get_language_from_extension(filename):
ext = Path(filename).suffix.lower()
for lang, info in LANGUAGES.items():
if ext in info['extensions']:
return lang, info['name']
return 'unknown', 'Unknown'
def is_binary_file(filepath):
try:
with open(filepath, 'tr') as check:
check.read(1024)
return False
except UnicodeDecodeError:
return True
def count_lines_with_comments(content, language_key):
lines = content.splitlines()
total_lines = len(lines)
lang_info = LANGUAGES.get(language_key, {})
comment_regex_str = lang_info.get('comment_regex')
code_lines = 0
comment_lines = 0
empty_lines = 0
in_multiline_comment = False
comment_regex = None
if comment_regex_str:
comment_regex = re.compile(comment_regex_str, re.MULTILINE)
for line in lines:
stripped = line.strip()
if not stripped:
empty_lines += 1
continue
if in_multiline_comment:
comment_lines += 1
if '*/' in line:
in_multiline_comment = False
continue
if '/*' in stripped and '*/' not in stripped:
comment_lines += 1
in_multiline_comment = True
continue
is_comment = False
if comment_regex:
if comment_regex.match(stripped):
comment_lines += 1
is_comment = True
if not is_comment:
code_lines += 1
return {
'total_lines': total_lines,
'code_lines': code_lines,
'comment_lines': comment_lines,
'empty_lines': empty_lines
}
def analyze_file(filepath):
rel_path = str(filepath.relative_to(Path.cwd()))
filename = filepath.name
if is_binary_file(filepath):
return {
'path': rel_path,
'name': filename,
'type': 'binary',
'size': filepath.stat().st_size,
'language': 'binary',
'language_name': 'Binary'
}
lang_key, lang_name = get_language_from_extension(filename)
try:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
lines_stats = count_lines_with_comments(content, lang_key)
file_hash = hashlib.sha256(content.encode('utf-8')).hexdigest()
return {
'path': rel_path,
'name': filename,
'type': 'text',
'size': filepath.stat().st_size,
'language': lang_key,
'language_name': lang_name,
'hash': file_hash,
'lines': lines_stats['total_lines'],
'code_lines': lines_stats['code_lines'],
'comment_lines': lines_stats['comment_lines'],
'empty_lines': lines_stats['empty_lines']
}
except Exception as e:
print(f"Error analyzing {rel_path}: {e}")
return None
print("πŸš€ Starting enhanced repository analysis...")
repo_full_name = os.environ.get('GITHUB_REPOSITORY', 'unknown')
event_name = os.environ.get('GITHUB_EVENT_NAME', 'unknown')
ref = os.environ.get('GITHUB_REF', 'unknown')
actor = os.environ.get('GITHUB_ACTOR', 'unknown')
sha = os.environ.get('GITHUB_SHA', 'unknown')
run_id = os.environ.get('GITHUB_RUN_ID', 'unknown')
run_number = os.environ.get('GITHUB_RUN_NUMBER', 'unknown')
run_attempt = os.environ.get('GITHUB_RUN_ATTEMPT', 'unknown')
repo_root = Path.cwd()
files_analyzed = []
language_stats = defaultdict(lambda: {
'files': 0,
'total_lines': 0,
'code_lines': 0,
'comment_lines': 0,
'empty_lines': 0,
'size': 0
})
exclude_dirs = {'.git', '.github', 'node_modules', '__pycache__', 'venv', 'env', '.venv', 'dist', 'build', '.next', '.nuxt'}
exclude_files = {'enhanced_analyzer.py', 'repo-analysis.yml', '.gitkeep'}
for filepath in repo_root.rglob('*'):
if filepath.is_file():
should_exclude = any(excl in filepath.parts for excl in exclude_dirs)
if should_exclude:
continue
# Skip the analyzer script and workflow file themselves
if filepath.name in exclude_files:
continue
result = analyze_file(filepath)
if result:
files_analyzed.append(result)
lang = result['language']
if lang != 'binary':
language_stats[lang]['files'] += 1
language_stats[lang]['total_lines'] += result.get('lines', 0)
language_stats[lang]['code_lines'] += result.get('code_lines', 0)
language_stats[lang]['comment_lines'] += result.get('comment_lines', 0)
language_stats[lang]['empty_lines'] += result.get('empty_lines', 0)
language_stats[lang]['size'] += result.get('size', 0)
total_files = len(files_analyzed)
text_files = sum(1 for f in files_analyzed if f['type'] == 'text')
binary_files = total_files - text_files
total_code_lines = sum(stats['code_lines'] for stats in language_stats.values())
total_lines = sum(stats['total_lines'] for stats in language_stats.values())
languages = {}
for lang, stats in sorted(language_stats.items(), key=lambda x: x[1]['code_lines'], reverse=True):
lang_info = LANGUAGES.get(lang, {'name': lang.capitalize()})
languages[lang] = {
'name': lang_info['name'],
'files': stats['files'],
'lines': stats['total_lines'],
'code_lines': stats['code_lines'],
'comment_lines': stats['comment_lines'],
'empty_lines': stats['empty_lines'],
'percentage': round((stats['code_lines'] / total_code_lines * 100), 2) if total_code_lines > 0 else 0,
'size_mb': round(stats['size'] / (1024 * 1024), 2)
}
result = {
'repository': repo_full_name,
'full_name': repo_full_name,
'event': event_name,
'ref': ref,
'branch': ref.replace('refs/heads/', '') if ref.startswith('refs/heads/') else ref,
'actor': actor,
'commit_sha': sha,
'run_id': run_id,
'run_number': int(run_number),
'run_attempt': int(run_attempt),
'analyzed_at': datetime.utcnow().isoformat() + 'Z',
'summary': {
'total_files': total_files,
'text_files': text_files,
'binary_files': binary_files,
'total_lines': total_lines,
'code_lines': total_code_lines,
'comment_lines': sum(stats['comment_lines'] for stats in language_stats.values()),
'empty_lines': sum(stats['empty_lines'] for stats in language_stats.values()),
'unique_languages': len(languages),
'total_size_mb': round(sum(f.get('size', 0) for f in files_analyzed) / (1024 * 1024), 2)
},
'languages': languages,
'files': files_analyzed[:500]
}
with open('enhanced_analysis_result.json', 'w', encoding='utf-8') as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"πŸ“Š Repository Analysis Complete:")
print(f" Repository: {result['repository']}")
print(f" Total Files: {result['summary']['total_files']}")
print(f" Code Lines: {result['summary']['code_lines']:,}")
print(f" Languages: {result['summary']['unique_languages']}")
print(f" Total Size: {result['summary']['total_size_mb']} MB")
print(f"πŸ“ˆ Top Languages by Code:")
for lang, stats in list(languages.items())[:5]:
print(f" - {stats['name']}: {stats['code_lines']:,} lines ({stats['percentage']}%)")
PYTHON_SCRIPT
python3 enhanced_analyzer.py
- name: Send analysis to external service
env:
WEBHOOK_ENDPOINT: ${{ secrets.WEBHOOK_ENDPOINT }}
WEBHOOK_API_TOKEN: ${{ secrets.WEBHOOK_API_TOKEN }}
run: |
# ⏳ WAIT FOR SECRETS: GitHub App sets secrets just before pushing the workflow.
# On the very first run the secrets may still be propagating β€” retry up to 5 times.
MAX_WAIT=5
WAIT_COUNT=0
until [ -n "$WEBHOOK_ENDPOINT" ] && [ -n "$WEBHOOK_API_TOKEN" ]; do
if [ "$WAIT_COUNT" -ge "$MAX_WAIT" ]; then
echo "❌ Secrets (WEBHOOK_ENDPOINT / WEBHOOK_API_TOKEN) are not available after ${MAX_WAIT} retries."
echo "Please check that the GitHub App has write access to repository secrets and re-install/re-add the repository."
exit 1
fi
echo "⏳ Secrets not yet available β€” waiting 10 s (attempt $((WAIT_COUNT+1))/$MAX_WAIT)..."
sleep 10
WAIT_COUNT=$((WAIT_COUNT+1))
# Re-read secrets from the environment (GitHub populates them at job start,
# so we re-export from the workflow context on each retry iteration)
WEBHOOK_ENDPOINT="${{ secrets.WEBHOOK_ENDPOINT }}"
WEBHOOK_API_TOKEN="${{ secrets.WEBHOOK_API_TOKEN }}"
done
# πŸ”₯ FAIL FAST: Final hard check
if [ -z "$WEBHOOK_ENDPOINT" ]; then
echo "❌ WEBHOOK_ENDPOINT secret is not set!"
echo "Please ensure the GitHub App has pushed the required secrets to this repository."
exit 1
fi
if [ -z "$WEBHOOK_API_TOKEN" ]; then
echo "❌ WEBHOOK_API_TOKEN secret is not set!"
echo "Please ensure the GitHub App has pushed the required secrets to this repository."
exit 1
fi
if [ -f enhanced_analysis_result.json ]; then
echo "Sending analysis to $WEBHOOK_ENDPOINT"
temp_response=$(mktemp)
http_code=$(curl -L -X POST "$WEBHOOK_ENDPOINT" -H "Content-Type: application/json" -H "Accept: application/json" -H "X-API-Token: $WEBHOOK_API_TOKEN" -H "X-GitHub-Event: ${{ github.event_name }}" -H "X-Repository: ${{ github.repository }}" -H "X-Run-ID: ${{ github.run_id }}" -d @enhanced_analysis_result.json --max-time 30 --connect-timeout 10 -w "%{http_code}" -o "$temp_response" -s --location --fail 2>&1) || true
if [ -f "$temp_response" ]; then
response_body=$(cat "$temp_response")
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
echo "βœ… Analysis sent successfully (HTTP $http_code)"
echo "Response: $response_body"
else
echo "❌ Failed to send analysis (HTTP $http_code)"
echo "Response: $response_body"
# πŸ”₯ SAFER RETRY: Only retry if we have a valid endpoint
if [ -n "$WEBHOOK_ENDPOINT" ] && [ "$WEBHOOK_ENDPOINT" != "/" ]; then
if [[ "$WEBHOOK_ENDPOINT" == */ ]]; then
alt_endpoint="${WEBHOOK_ENDPOINT%/}"
else
alt_endpoint="${WEBHOOK_ENDPOINT}/"
fi
echo "Retrying with alternative endpoint: $alt_endpoint"
http_code2=$(curl -L -X POST "$alt_endpoint" -H "Content-Type: application/json" -H "Accept: application/json" -H "X-API-Token: $WEBHOOK_API_TOKEN" -d @enhanced_analysis_result.json --max-time 30 -w "%{http_code}" -o "$temp_response" -s --location)
if [ "$http_code2" -ge 200 ] && [ "$http_code2" -lt 300 ]; then
echo "βœ… Analysis sent successfully on retry (HTTP $http_code2)"
echo "Response: $(cat "$temp_response")"
else
echo "❌ Failed to send analysis on retry (HTTP $http_code2)"
cat "$temp_response"
exit 1
fi
else
echo "❌ Cannot retry: WEBHOOK_ENDPOINT is empty or invalid"
exit 1
fi
fi
rm -f "$temp_response"
else
echo "❌ No response received"
exit 1
fi
else
echo "❌ Analysis file not found"
exit 1
fi
- name: Upload analysis artifact
uses: actions/upload-artifact@v4.6.2
with:
name: enhanced-analysis-${{ github.run_id }}
path: enhanced_analysis_result.json
retention-days: 7