add workflows
This commit is contained in:
parent
f272748be4
commit
cff78d1230
6 changed files with 704 additions and 16 deletions
18
.forgejo/workflows/project-report.yml
Normal file
18
.forgejo/workflows/project-report.yml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
name: Project Report
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
project-report:
|
||||||
|
runs-on: linux-dev
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Generate project report
|
||||||
|
run: |
|
||||||
|
chmod +x tools/project-report.sh
|
||||||
|
./tools/project-report.sh
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
name: Validate Code
|
name: Validate Code
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
|
@ -10,21 +9,10 @@ jobs:
|
||||||
runs-on: linux-dev
|
runs-on: linux-dev
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Check PHP syntax
|
- name: Run validation
|
||||||
run: |
|
run: |
|
||||||
if find . -name "*.php" | grep -q .; then
|
chmod +x tools/validate-code.sh
|
||||||
find . -name "*.php" -print0 | xargs -0 -n1 php -l
|
./tools/validate-code.sh
|
||||||
else
|
|
||||||
echo "No PHP files found."
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Check C# build
|
|
||||||
run: |
|
|
||||||
if find . -name "*.csproj" | grep -q .; then
|
|
||||||
dotnet build
|
|
||||||
else
|
|
||||||
echo "No C# project found."
|
|
||||||
fi
|
|
||||||
|
|
|
||||||
108
docs/AUTOMATION.md
Normal file
108
docs/AUTOMATION.md
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
# Automation
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This repository includes two Forgejo Actions workflows for reusable customer-project automation:
|
||||||
|
|
||||||
|
- `validate.yml`: runs repository validation on push and pull request.
|
||||||
|
- `project-report.yml`: generates best-effort project analysis reports on push and pull request.
|
||||||
|
|
||||||
|
## Workflows
|
||||||
|
|
||||||
|
### `validate.yml`
|
||||||
|
|
||||||
|
- runs on `push`
|
||||||
|
- runs on `pull_request`
|
||||||
|
- uses the `linux-dev` runner label
|
||||||
|
- checks out the repository
|
||||||
|
- runs `tools/validate-code.sh`
|
||||||
|
- fails when validation finds real errors
|
||||||
|
|
||||||
|
### `project-report.yml`
|
||||||
|
|
||||||
|
- runs on `push`
|
||||||
|
- runs on `pull_request`
|
||||||
|
- uses the `linux-dev` runner label
|
||||||
|
- checks out the repository
|
||||||
|
- runs `tools/project-report.sh`
|
||||||
|
- regenerates reports in `reports/`
|
||||||
|
- does not fail because analysis finds issues or uncertainty
|
||||||
|
- only fails if the script itself crashes
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
### `tools/validate-code.sh`
|
||||||
|
|
||||||
|
This script recursively scans the repository while excluding common generated or dependency folders such as:
|
||||||
|
|
||||||
|
- `.git`
|
||||||
|
- `.forgejo`
|
||||||
|
- `node_modules`
|
||||||
|
- `vendor`
|
||||||
|
- `Library`
|
||||||
|
- `Temp`
|
||||||
|
- `Logs`
|
||||||
|
- `obj`
|
||||||
|
- `bin`
|
||||||
|
|
||||||
|
When the necessary tools are installed, it validates:
|
||||||
|
|
||||||
|
- PHP with `php -l`
|
||||||
|
- Python with `python3 -m py_compile`
|
||||||
|
- Ruby with `ruby -c`
|
||||||
|
- Perl with `perl -c`
|
||||||
|
- Shell with `bash -n`
|
||||||
|
- JavaScript with `node --check`
|
||||||
|
- JSON with `jq`
|
||||||
|
- YAML with `yamllint`
|
||||||
|
- HTML with `tidy`
|
||||||
|
- C# with `dotnet build`
|
||||||
|
- C/C++ CMake projects with `cmake` configure validation
|
||||||
|
|
||||||
|
It writes `reports/errors.md` with:
|
||||||
|
|
||||||
|
- validation summary
|
||||||
|
- files checked
|
||||||
|
- warnings
|
||||||
|
- errors
|
||||||
|
- detailed per-file results
|
||||||
|
|
||||||
|
### `tools/project-report.sh`
|
||||||
|
|
||||||
|
This script generates:
|
||||||
|
|
||||||
|
- `reports/project-flow.md`
|
||||||
|
- `reports/file-summary.md`
|
||||||
|
- `reports/languages.md`
|
||||||
|
|
||||||
|
It performs best-effort analysis for:
|
||||||
|
|
||||||
|
- language counts
|
||||||
|
- likely entry points
|
||||||
|
- possible startup files
|
||||||
|
- framework detection
|
||||||
|
- database detection
|
||||||
|
- environment files
|
||||||
|
- config files
|
||||||
|
- Docker-related files
|
||||||
|
- probable application flow
|
||||||
|
|
||||||
|
The wording is intentionally cautious. It does not claim certainty.
|
||||||
|
|
||||||
|
## Reuse In Future Customer Repositories
|
||||||
|
|
||||||
|
To reuse this automation in another repository:
|
||||||
|
|
||||||
|
1. Copy `.forgejo/workflows/validate.yml`
|
||||||
|
2. Copy `.forgejo/workflows/project-report.yml`
|
||||||
|
3. Copy `tools/validate-code.sh`
|
||||||
|
4. Copy `tools/project-report.sh`
|
||||||
|
5. Create `reports/.gitkeep`
|
||||||
|
6. Keep the exclude list if the repository includes generated or dependency folders
|
||||||
|
7. Review language and framework detection rules for project-specific needs
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Reports are regenerated on every run.
|
||||||
|
- Validation skips checks gracefully when required tools are not installed.
|
||||||
|
- The automation is additive and does not modify customer source files.
|
||||||
1
reports/.gitkeep
Normal file
1
reports/.gitkeep
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
309
tools/project-report.sh
Executable file
309
tools/project-report.sh
Executable 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
|
||||||
264
tools/validate-code.sh
Executable file
264
tools/validate-code.sh
Executable file
|
|
@ -0,0 +1,264 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
REPORT_DIR="$ROOT_DIR/reports"
|
||||||
|
ERROR_REPORT="$REPORT_DIR/errors.md"
|
||||||
|
|
||||||
|
FILES_CHECKED=0
|
||||||
|
WARNINGS=0
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
DETAILS=()
|
||||||
|
|
||||||
|
EXCLUDES=(
|
||||||
|
".git"
|
||||||
|
".forgejo"
|
||||||
|
"node_modules"
|
||||||
|
"vendor"
|
||||||
|
"Library"
|
||||||
|
"Temp"
|
||||||
|
"Logs"
|
||||||
|
"obj"
|
||||||
|
"bin"
|
||||||
|
)
|
||||||
|
|
||||||
|
mkdir -p "$REPORT_DIR"
|
||||||
|
|
||||||
|
escape_md() {
|
||||||
|
printf '%s' "$1" | sed 's/|/\\|/g'
|
||||||
|
}
|
||||||
|
|
||||||
|
have_tool() {
|
||||||
|
command -v "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
add_warning() {
|
||||||
|
local message="$1"
|
||||||
|
WARNINGS=$((WARNINGS + 1))
|
||||||
|
DETAILS+=("- Warning: $(escape_md "$message")")
|
||||||
|
}
|
||||||
|
|
||||||
|
add_error() {
|
||||||
|
local message="$1"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
DETAILS+=("- Error: $(escape_md "$message")")
|
||||||
|
}
|
||||||
|
|
||||||
|
add_ok() {
|
||||||
|
local message="$1"
|
||||||
|
DETAILS+=("- OK: $(escape_md "$message")")
|
||||||
|
}
|
||||||
|
|
||||||
|
relative_path() {
|
||||||
|
local path="$1"
|
||||||
|
path="${path#"$ROOT_DIR"/}"
|
||||||
|
printf '%s' "$path"
|
||||||
|
}
|
||||||
|
|
||||||
|
join_by() {
|
||||||
|
local delimiter="$1"
|
||||||
|
shift || true
|
||||||
|
local first=1
|
||||||
|
local item
|
||||||
|
for item in "$@"; do
|
||||||
|
if [[ $first -eq 1 ]]; then
|
||||||
|
printf '%s' "$item"
|
||||||
|
first=0
|
||||||
|
else
|
||||||
|
printf '%s%s' "$delimiter" "$item"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
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[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_check() {
|
||||||
|
local label="$1"
|
||||||
|
local file="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
FILES_CHECKED=$((FILES_CHECKED + 1))
|
||||||
|
|
||||||
|
if "$@" >/tmp/runlevel-validate.out 2>/tmp/runlevel-validate.err; then
|
||||||
|
add_ok "$label passed: $(relative_path "$file")"
|
||||||
|
else
|
||||||
|
local stdout stderr combined
|
||||||
|
stdout="$(tr '\n' ' ' </tmp/runlevel-validate.out | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
|
||||||
|
stderr="$(tr '\n' ' ' </tmp/runlevel-validate.err | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
|
||||||
|
combined="$(join_by ' | ' "${stdout:-}" "${stderr:-}")"
|
||||||
|
add_error "$label failed: $(relative_path "$file")${combined:+ | $combined}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_repo_build_checks() {
|
||||||
|
local sln_files=()
|
||||||
|
local csproj_files=()
|
||||||
|
local cmake_file="$ROOT_DIR/CMakeLists.txt"
|
||||||
|
local tmp_dir
|
||||||
|
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
sln_files+=("$file")
|
||||||
|
done < <(find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -name '*.sln' -type f -print0)
|
||||||
|
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
csproj_files+=("$file")
|
||||||
|
done < <(find "$ROOT_DIR" -path "$ROOT_DIR/.git" -prune -o -name '*.csproj' -type f -print0)
|
||||||
|
|
||||||
|
if ((${#sln_files[@]} > 0 || ${#csproj_files[@]} > 0)); then
|
||||||
|
if have_tool dotnet; then
|
||||||
|
if ((${#sln_files[@]} > 0)); then
|
||||||
|
for file in "${sln_files[@]}"; do
|
||||||
|
run_check "dotnet build" "$file" dotnet build "$file" --nologo --verbosity minimal
|
||||||
|
done
|
||||||
|
else
|
||||||
|
for file in "${csproj_files[@]}"; do
|
||||||
|
run_check "dotnet build" "$file" dotnet build "$file" --nologo --verbosity minimal
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
add_warning "dotnet not installed; skipping C# validation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$cmake_file" ]]; then
|
||||||
|
if have_tool cmake; then
|
||||||
|
FILES_CHECKED=$((FILES_CHECKED + 1))
|
||||||
|
tmp_dir="$(mktemp -d)"
|
||||||
|
if cmake -S "$ROOT_DIR" -B "$tmp_dir" >/tmp/runlevel-validate.out 2>/tmp/runlevel-validate.err; then
|
||||||
|
add_ok "cmake configure passed: CMakeLists.txt"
|
||||||
|
else
|
||||||
|
local stdout stderr combined
|
||||||
|
stdout="$(tr '\n' ' ' </tmp/runlevel-validate.out | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
|
||||||
|
stderr="$(tr '\n' ' ' </tmp/runlevel-validate.err | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
|
||||||
|
combined="$(join_by ' | ' "${stdout:-}" "${stderr:-}")"
|
||||||
|
add_error "cmake configure failed: CMakeLists.txt${combined:+ | $combined}"
|
||||||
|
fi
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
else
|
||||||
|
add_warning "cmake not installed; skipping C/C++ validation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_file() {
|
||||||
|
local file="$1"
|
||||||
|
local base
|
||||||
|
|
||||||
|
base="$(basename "$file")"
|
||||||
|
|
||||||
|
case "$file" in
|
||||||
|
*.php)
|
||||||
|
if have_tool php; then
|
||||||
|
run_check "php -l" "$file" php -l "$file"
|
||||||
|
else
|
||||||
|
add_warning "php not installed; skipping $(relative_path "$file")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*.py)
|
||||||
|
if have_tool python3; then
|
||||||
|
run_check "python3 -m py_compile" "$file" python3 -m py_compile "$file"
|
||||||
|
else
|
||||||
|
add_warning "python3 not installed; skipping $(relative_path "$file")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*.rb)
|
||||||
|
if have_tool ruby; then
|
||||||
|
run_check "ruby -c" "$file" ruby -c "$file"
|
||||||
|
else
|
||||||
|
add_warning "ruby not installed; skipping $(relative_path "$file")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*.pl|*.pm)
|
||||||
|
if have_tool perl; then
|
||||||
|
run_check "perl -c" "$file" perl -c "$file"
|
||||||
|
else
|
||||||
|
add_warning "perl not installed; skipping $(relative_path "$file")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*.sh|*.bash)
|
||||||
|
if have_tool bash; then
|
||||||
|
run_check "bash -n" "$file" bash -n "$file"
|
||||||
|
else
|
||||||
|
add_warning "bash not installed; skipping $(relative_path "$file")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*.js|*.mjs|*.cjs)
|
||||||
|
if have_tool node; then
|
||||||
|
run_check "node --check" "$file" node --check "$file"
|
||||||
|
else
|
||||||
|
add_warning "node not installed; skipping $(relative_path "$file")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*.json)
|
||||||
|
if have_tool jq; then
|
||||||
|
run_check "jq validation" "$file" jq empty "$file"
|
||||||
|
else
|
||||||
|
add_warning "jq not installed; skipping $(relative_path "$file")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*.yml|*.yaml)
|
||||||
|
if have_tool yamllint; then
|
||||||
|
run_check "yamllint" "$file" yamllint -f parsable "$file"
|
||||||
|
else
|
||||||
|
add_warning "yamllint not installed; skipping $(relative_path "$file")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*.html|*.htm)
|
||||||
|
if have_tool tidy; then
|
||||||
|
run_check "tidy validation" "$file" tidy -qe "$file"
|
||||||
|
else
|
||||||
|
add_warning "tidy not installed; skipping $(relative_path "$file")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ "$base" == "Dockerfile" ]]; then
|
||||||
|
FILES_CHECKED=$((FILES_CHECKED + 1))
|
||||||
|
add_ok "Detected Dockerfile: $(relative_path "$file")"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
validate_file "$file"
|
||||||
|
done < <(collect_files)
|
||||||
|
|
||||||
|
run_repo_build_checks
|
||||||
|
|
||||||
|
{
|
||||||
|
printf '# Validation Summary\n\n'
|
||||||
|
printf -- '- Files Checked: %s\n' "$FILES_CHECKED"
|
||||||
|
printf -- '- Warnings: %s\n' "$WARNINGS"
|
||||||
|
printf -- '- Errors: %s\n\n' "$ERRORS"
|
||||||
|
printf '## Detailed Results\n\n'
|
||||||
|
if ((${#DETAILS[@]} == 0)); then
|
||||||
|
printf 'No validation steps were run.\n'
|
||||||
|
else
|
||||||
|
printf '%s\n' "${DETAILS[@]}"
|
||||||
|
fi
|
||||||
|
} >"$ERROR_REPORT"
|
||||||
|
|
||||||
|
rm -f /tmp/runlevel-validate.out /tmp/runlevel-validate.err
|
||||||
|
|
||||||
|
if ((ERRORS > 0)); then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
Loading…
Add table
Add a link
Reference in a new issue