add workflows
Some checks failed
Project Report / project-report (push) Successful in 4s
Validate Code / validate (push) Failing after 3s

This commit is contained in:
Frank Harris 2026-06-12 14:40:11 -05:00
parent f272748be4
commit cff78d1230
6 changed files with 704 additions and 16 deletions

309
tools/project-report.sh Executable file
View file

@ -0,0 +1,309 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
REPORT_DIR="$ROOT_DIR/reports"
FLOW_REPORT="$REPORT_DIR/project-flow.md"
FILE_REPORT="$REPORT_DIR/file-summary.md"
LANG_REPORT="$REPORT_DIR/languages.md"
EXCLUDES=(
".git"
".forgejo"
"node_modules"
"vendor"
"Library"
"Temp"
"Logs"
"obj"
"bin"
)
mkdir -p "$REPORT_DIR"
have_tool() {
command -v "$1" >/dev/null 2>&1
}
collect_files() {
local find_args=("$ROOT_DIR")
local exclude
if ((${#EXCLUDES[@]} > 0)); then
find_args+=("(")
for exclude in "${EXCLUDES[@]}"; do
find_args+=(-name "$exclude" -o)
done
unset 'find_args[${#find_args[@]}-1]'
find_args+=(")" -prune -o)
fi
find_args+=(-type f -print0)
find "${find_args[@]}"
}
relative_path() {
local path="$1"
path="${path#"$ROOT_DIR"/}"
printf '%s' "$path"
}
search_tree() {
local pattern="$1"
if have_tool rg; then
rg -qi "$pattern" "$ROOT_DIR" --glob '!node_modules/**' --glob '!vendor/**' --glob '!.git/**' --glob '!Library/**' --glob '!Temp/**' --glob '!Logs/**' --glob '!obj/**' --glob '!bin/**'
else
grep -RqiE --exclude-dir=.git --exclude-dir=.forgejo --exclude-dir=node_modules --exclude-dir=vendor --exclude-dir=Library --exclude-dir=Temp --exclude-dir=Logs --exclude-dir=obj --exclude-dir=bin "$pattern" "$ROOT_DIR"
fi
}
count_matches() {
local pattern="$1"
find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -type f -name "$pattern" -print | wc -l | tr -d ' '
}
declare -A LANGUAGE_COUNTS=(
["PHP"]=0
["JavaScript"]=0
["TypeScript"]=0
["Python"]=0
["C#"]=0
["C++"]=0
["Ruby"]=0
["Perl"]=0
["HTML"]=0
["CSS"]=0
["Shell"]=0
)
ENTRY_POINTS=()
STARTUP_SCRIPTS=()
TOP_LEVEL_DIRS=()
ENV_FILES=()
CONFIG_FILES=()
DOCKER_FILES=()
DB_CONFIGS=()
detect_entry_or_startup() {
local file="$1"
local base
base="$(basename "$file")"
case "$base" in
index.php|index.html|index.htm|main.py|app.py|server.py|Program.cs|Main.cs|main.cpp|main.c)
ENTRY_POINTS+=("$(relative_path "$file")")
;;
start*.sh|launch*.sh|run*.sh|startup*.sh|*.bat|*.cmd)
STARTUP_SCRIPTS+=("$(relative_path "$file")")
;;
esac
}
detect_config_files() {
local file="$1"
local rel
rel="$(relative_path "$file")"
case "$(basename "$file")" in
.env|.env.*)
ENV_FILES+=("$rel")
;;
Dockerfile|docker-compose.yml|docker-compose.yaml|compose.yml|compose.yaml)
DOCKER_FILES+=("$rel")
;;
esac
case "$file" in
*.conf|*.config|*.ini|*.env|*.yaml|*.yml|*.json|*config*.php|*config*.py|*config*.rb)
CONFIG_FILES+=("$rel")
;;
esac
case "$file" in
*database*|*db*config*|*mysql*|*pgsql*|*postgres*|*sqlite*|*mongo*)
DB_CONFIGS+=("$rel")
;;
esac
}
classify_language() {
local file="$1"
case "$file" in
*.php) LANGUAGE_COUNTS["PHP"]=$((LANGUAGE_COUNTS["PHP"] + 1)) ;;
*.js|*.mjs|*.cjs) LANGUAGE_COUNTS["JavaScript"]=$((LANGUAGE_COUNTS["JavaScript"] + 1)) ;;
*.ts|*.tsx) LANGUAGE_COUNTS["TypeScript"]=$((LANGUAGE_COUNTS["TypeScript"] + 1)) ;;
*.py) LANGUAGE_COUNTS["Python"]=$((LANGUAGE_COUNTS["Python"] + 1)) ;;
*.cs) LANGUAGE_COUNTS["C#"]=$((LANGUAGE_COUNTS["C#"] + 1)) ;;
*.cpp|*.cc|*.cxx|*.c|*.h|*.hpp) LANGUAGE_COUNTS["C++"]=$((LANGUAGE_COUNTS["C++"] + 1)) ;;
*.rb) LANGUAGE_COUNTS["Ruby"]=$((LANGUAGE_COUNTS["Ruby"] + 1)) ;;
*.pl|*.pm) LANGUAGE_COUNTS["Perl"]=$((LANGUAGE_COUNTS["Perl"] + 1)) ;;
*.html|*.htm) LANGUAGE_COUNTS["HTML"]=$((LANGUAGE_COUNTS["HTML"] + 1)) ;;
*.css) LANGUAGE_COUNTS["CSS"]=$((LANGUAGE_COUNTS["CSS"] + 1)) ;;
*.sh|*.bash) LANGUAGE_COUNTS["Shell"]=$((LANGUAGE_COUNTS["Shell"] + 1)) ;;
esac
}
detect_frameworks() {
local frameworks=()
[[ -f "$ROOT_DIR/artisan" ]] && frameworks+=("Laravel")
[[ -d "$ROOT_DIR/app/Config" ]] && frameworks+=("CodeIgniter")
[[ -f "$ROOT_DIR/wp-config.php" ]] && frameworks+=("WordPress")
[[ -f "$ROOT_DIR/manage.py" ]] && frameworks+=("Django")
[[ -f "$ROOT_DIR/app.py" ]] && search_tree "Flask" && frameworks+=("Flask")
[[ -f "$ROOT_DIR/package.json" ]] && frameworks+=("Node.js")
[[ -f "$ROOT_DIR/package.json" ]] && search_tree "\"express\"" && frameworks+=("Express")
[[ -f "$ROOT_DIR/package.json" ]] && search_tree "\"react\"" && frameworks+=("React")
[[ -f "$ROOT_DIR/package.json" ]] && search_tree "\"vue\"" && frameworks+=("Vue")
find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -name '*.csproj' -type f -print | grep -q . 2>/dev/null && frameworks+=("ASP.NET")
find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -name 'ProjectSettings.asset' -type f -print | grep -q . 2>/dev/null && frameworks+=("Unity")
[[ -d "$ROOT_DIR/src/Symfony" || -f "$ROOT_DIR/bin/console" ]] && frameworks+=("Symfony")
printf '%s\n' "${frameworks[@]}" | awk 'NF && !seen[$0]++'
}
detect_databases() {
local databases=()
search_tree "mysql" && databases+=("MySQL")
search_tree "mariadb" && databases+=("MariaDB")
search_tree "postgres|postgresql|pgsql" && databases+=("PostgreSQL")
search_tree "sqlite" && databases+=("SQLite")
search_tree "mongodb|mongo" && databases+=("MongoDB")
printf '%s\n' "${databases[@]}" | awk 'NF && !seen[$0]++'
}
while IFS= read -r -d '' file; do
classify_language "$file"
detect_entry_or_startup "$file"
detect_config_files "$file"
done < <(collect_files)
while IFS= read -r dir; do
TOP_LEVEL_DIRS+=("${dir#./}")
done < <(find "$ROOT_DIR" -mindepth 1 -maxdepth 1 -type d ! -name '.git' | sed "s#^$ROOT_DIR/##" | sort)
FRAMEWORKS="$(detect_frameworks | awk 'NF { if (count++) printf ", "; printf "%s", $0 }')"
DATABASES="$(detect_databases | awk 'NF { if (count++) printf ", "; printf "%s", $0 }')"
LIKELY_ENTRY="None detected"
if ((${#ENTRY_POINTS[@]} > 0)); then
LIKELY_ENTRY="${ENTRY_POINTS[0]}"
fi
{
printf '# Languages\n\n'
printf '| Language | Count |\n'
printf '|---|---:|\n'
for lang in "PHP" "JavaScript" "TypeScript" "Python" "C#" "C++" "Ruby" "Perl" "HTML" "CSS" "Shell"; do
printf '| %s | %s |\n' "$lang" "${LANGUAGE_COUNTS[$lang]}"
done
} >"$LANG_REPORT"
{
printf '# File Summary\n\n'
printf '## Top-Level Folders\n\n'
if ((${#TOP_LEVEL_DIRS[@]} == 0)); then
printf -- '- None detected\n'
else
printf -- '- %s\n' "${TOP_LEVEL_DIRS[@]}"
fi
printf '\n## File Counts By Type\n\n'
printf '| Type | Count |\n'
printf '|---|---:|\n'
printf '| PHP | %s |\n' "${LANGUAGE_COUNTS["PHP"]}"
printf '| JavaScript | %s |\n' "${LANGUAGE_COUNTS["JavaScript"]}"
printf '| TypeScript | %s |\n' "${LANGUAGE_COUNTS["TypeScript"]}"
printf '| Python | %s |\n' "${LANGUAGE_COUNTS["Python"]}"
printf '| C# | %s |\n' "${LANGUAGE_COUNTS["C#"]}"
printf '| C++ | %s |\n' "${LANGUAGE_COUNTS["C++"]}"
printf '| Ruby | %s |\n' "${LANGUAGE_COUNTS["Ruby"]}"
printf '| Perl | %s |\n' "${LANGUAGE_COUNTS["Perl"]}"
printf '| HTML | %s |\n' "${LANGUAGE_COUNTS["HTML"]}"
printf '| CSS | %s |\n' "${LANGUAGE_COUNTS["CSS"]}"
printf '| Shell | %s |\n' "${LANGUAGE_COUNTS["Shell"]}"
printf '\n## Largest Source Directories\n\n'
find "$ROOT_DIR" -mindepth 1 -maxdepth 2 -type d ! -path "$ROOT_DIR/.git*" ! -path "$ROOT_DIR/.forgejo*" -print0 \
| while IFS= read -r -d '' dir; do
count="$(find "$dir" -type f | wc -l | tr -d ' ')"
printf '%s\t%s\n' "$count" "$(relative_path "$dir")"
done | sort -rn | head -n 10 | awk -F '\t' '{printf "- %s files: %s\n", $1, $2}'
} >"$FILE_REPORT"
{
printf '# Project Flow Report\n\n'
printf '## Likely Entry Point\n\n'
printf -- '- Likely entry point: %s\n' "$LIKELY_ENTRY"
printf '\n## Possible Startup Files\n\n'
if ((${#ENTRY_POINTS[@]} == 0 && ${#STARTUP_SCRIPTS[@]} == 0)); then
printf -- '- No obvious startup files detected.\n'
else
if ((${#ENTRY_POINTS[@]} > 0)); then
printf -- '- Possible startup file: %s\n' "${ENTRY_POINTS[@]}"
fi
if ((${#STARTUP_SCRIPTS[@]} > 0)); then
printf -- '- Possible launch script: %s\n' "${STARTUP_SCRIPTS[@]}"
fi
fi
printf '\n## Detected Frameworks\n\n'
if [[ -n "$FRAMEWORKS" ]]; then
IFS=',' read -r -a framework_items <<<"$FRAMEWORKS"
for item in "${framework_items[@]}"; do
printf -- '- %s\n' "$(echo "$item" | sed 's/^ *//; s/ *$//')"
done
else
printf -- '- No supported framework markers were detected.\n'
fi
printf '\n## Detected Databases\n\n'
if [[ -n "$DATABASES" ]]; then
IFS=',' read -r -a database_items <<<"$DATABASES"
for item in "${database_items[@]}"; do
printf -- '- %s\n' "$(echo "$item" | sed 's/^ *//; s/ *$//')"
done
else
printf -- '- No database markers were detected.\n'
fi
printf '\n## Configuration Files\n\n'
if ((${#ENV_FILES[@]} > 0)); then
printf -- '- Possible .env files: %s\n' "${ENV_FILES[@]}"
else
printf -- '- No .env files detected.\n'
fi
if ((${#DOCKER_FILES[@]} > 0)); then
printf -- '- Docker-related files: %s\n' "${DOCKER_FILES[@]}"
else
printf -- '- No Docker-related files detected.\n'
fi
if ((${#DB_CONFIGS[@]} > 0)); then
printf -- '- Possible database config files: %s\n' "${DB_CONFIGS[@]}"
else
printf -- '- No obvious database config files detected.\n'
fi
printf '\n## Important Files To Review\n\n'
if ((${#ENTRY_POINTS[@]} > 0)); then
printf -- '- %s\n' "${ENTRY_POINTS[@]}"
fi
if ((${#CONFIG_FILES[@]} > 0)); then
printf -- '- %s\n' "${CONFIG_FILES[@]:0:10}"
else
printf -- '- No common config files detected.\n'
fi
printf '\n## Probable Application Flow\n\n'
printf 'This is a best-effort analysis only.\n\n'
printf -- '- Probable application flow starts near `%s`.\n' "$LIKELY_ENTRY"
printf -- '- Likely supporting behavior is configured through nearby config files, environment files, or workflow files.\n'
printf -- '- If framework markers are present, startup and routing probably follow that framework'"'"'s conventions.\n'
printf -- '- If no framework markers are present, the repository likely uses a simpler file-based startup flow.\n'
} >"$FLOW_REPORT"
exit 0