Update repo_health.yml
This commit is contained in:
165
.github/workflows/repo_health.yml
vendored
165
.github/workflows/repo_health.yml
vendored
@@ -29,6 +29,14 @@
|
|||||||
|
|
||||||
name: Joomla Repo Health
|
name: Joomla Repo Health
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: repo-health-${{ github.repository }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
@@ -54,6 +62,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
access_check:
|
access_check:
|
||||||
|
timeout-minutes: 10
|
||||||
name: Access control
|
name: Access control
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -104,6 +113,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
release_config:
|
release_config:
|
||||||
|
timeout-minutes: 20
|
||||||
name: Release configuration
|
name: Release configuration
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [access_check]
|
needs: [access_check]
|
||||||
@@ -206,7 +216,7 @@ jobs:
|
|||||||
|
|
||||||
mkdir -p "$HOME/.ssh"
|
mkdir -p "$HOME/.ssh"
|
||||||
key_file="$HOME/.ssh/ci_sftp_key"
|
key_file="$HOME/.ssh/ci_sftp_key"
|
||||||
printf '%s\\n' "${FTP_KEY}" > "${key_file}"
|
printf '%s\n' "${FTP_KEY}" > "${key_file}"
|
||||||
chmod 600 "${key_file}"
|
chmod 600 "${key_file}"
|
||||||
|
|
||||||
# If FTP_PASSWORD is present, treat it as the private key passphrase and decrypt the key in place.
|
# If FTP_PASSWORD is present, treat it as the private key passphrase and decrypt the key in place.
|
||||||
@@ -226,13 +236,25 @@ jobs:
|
|||||||
echo "### SFTP connectivity test" >> "${GITHUB_STEP_SUMMARY}"
|
echo "### SFTP connectivity test" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
echo "Attempting non-destructive SFTP session (pwd only)." >> "${GITHUB_STEP_SUMMARY}"
|
echo "Attempting non-destructive SFTP session (pwd only)." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
|
||||||
printf 'pwd\\nbye\\n' | sftp -oBatchMode=yes -oStrictHostKeyChecking=no -P "${port}" -i "${key_file}" "${FTP_USER}@${FTP_HOST}"
|
printf 'pwd\nbye\n' | sftp -oBatchMode=yes -oStrictHostKeyChecking=no -P "${port}" -i "${key_file}" "${FTP_USER}@${FTP_HOST}" >/tmp/sftp_check.log 2>&1
|
||||||
|
sftp_rc=$?
|
||||||
|
|
||||||
echo "SFTP connectivity check passed." >> "${GITHUB_STEP_SUMMARY}"
|
if [ "${sftp_rc}" -eq 0 ]; then
|
||||||
|
echo "### SFTP connectivity result" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
echo "Status: SUCCESS" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
else
|
||||||
|
echo "### SFTP connectivity result" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
echo "Status: FAILED (exit code ${sftp_rc})" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
echo "" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
echo "Last SFTP output:" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
tail -n 10 /tmp/sftp_check.log >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
scripts_config:
|
scripts_config:
|
||||||
name: Scripts and tooling
|
name: Scripts and tooling
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
needs: [access_check]
|
needs: [access_check]
|
||||||
if: ${{ needs.access_check.outputs.allowed == 'true' }}
|
if: ${{ needs.access_check.outputs.allowed == 'true' }}
|
||||||
permissions:
|
permissions:
|
||||||
@@ -325,13 +347,16 @@ jobs:
|
|||||||
command -v xmllint >/dev/null 2>&1 && tool_status+=("xmllint") || true
|
command -v xmllint >/dev/null 2>&1 && tool_status+=("xmllint") || true
|
||||||
command -v shellcheck >/dev/null 2>&1 && tool_status+=("shellcheck") || true
|
command -v shellcheck >/dev/null 2>&1 && tool_status+=("shellcheck") || true
|
||||||
|
|
||||||
export MISSING_DIRS="$(printf '%s\n' "${missing_dirs[@]:-}")"
|
export MISSING_DIRS="$(printf '%s
|
||||||
export MISSING_FILES="$(printf '%s\n' "${missing_files[@]:-}")"
|
' "${missing_dirs[@]:-}")"
|
||||||
|
export MISSING_FILES="$(printf '%s
|
||||||
|
' "${missing_files[@]:-}")"
|
||||||
export TOOLS="${tool_status[*]:-}"
|
export TOOLS="${tool_status[*]:-}"
|
||||||
|
|
||||||
report_json="$(python3 - <<'PY'
|
report_json="$(python3 - <<'PY'
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
profile = os.environ.get('PROFILE_RAW') or 'all'
|
profile = os.environ.get('PROFILE_RAW') or 'all'
|
||||||
required_script_dirs = [
|
required_script_dirs = [
|
||||||
"scripts/fix",
|
"scripts/fix",
|
||||||
@@ -352,9 +377,13 @@ jobs:
|
|||||||
"scripts/validate/no_secrets.sh",
|
"scripts/validate/no_secrets.sh",
|
||||||
"scripts/validate/license_headers.sh",
|
"scripts/validate/license_headers.sh",
|
||||||
]
|
]
|
||||||
missing_dirs = os.environ.get('MISSING_DIRS','').split('\n') if os.environ.get('MISSING_DIRS') else []
|
|
||||||
missing_files = os.environ.get('MISSING_FILES','').split('\n') if os.environ.get('MISSING_FILES') else []
|
missing_dirs = os.environ.get('MISSING_DIRS','').split('
|
||||||
|
') if os.environ.get('MISSING_DIRS') else []
|
||||||
|
missing_files = os.environ.get('MISSING_FILES','').split('
|
||||||
|
') if os.environ.get('MISSING_FILES') else []
|
||||||
tools = os.environ.get('TOOLS','').split() if os.environ.get('TOOLS') else []
|
tools = os.environ.get('TOOLS','').split() if os.environ.get('TOOLS') else []
|
||||||
|
|
||||||
out = {
|
out = {
|
||||||
"profile": profile,
|
"profile": profile,
|
||||||
"checked": {
|
"checked": {
|
||||||
@@ -365,6 +394,7 @@ jobs:
|
|||||||
"missing_files": [x for x in missing_files if x],
|
"missing_files": [x for x in missing_files if x],
|
||||||
"tools_available": tools,
|
"tools_available": tools,
|
||||||
}
|
}
|
||||||
|
|
||||||
print(json.dumps(out, indent=2))
|
print(json.dumps(out, indent=2))
|
||||||
PY
|
PY
|
||||||
)"
|
)"
|
||||||
@@ -436,9 +466,77 @@ jobs:
|
|||||||
|
|
||||||
echo "Shell quality gate passed." >> "${GITHUB_STEP_SUMMARY}"
|
echo "Shell quality gate passed." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "### Guardrails: scripts and tooling"
|
||||||
|
echo "Tools available: ${tool_status[*]:-none}"
|
||||||
|
echo ""
|
||||||
|
echo "### Guardrails report (JSON)"
|
||||||
|
echo "```json"
|
||||||
|
echo "${report_json}"
|
||||||
|
echo "```"
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
|
||||||
|
if [ "${#missing_dirs[@]}" -gt 0 ]; then
|
||||||
|
echo "### Missing required script directories" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
for m in "${missing_dirs[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
|
||||||
|
echo "ERROR: Guardrails failed. Missing required script directories." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
|
||||||
|
echo "### Unapproved script directories detected" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
for m in "${unapproved_dirs[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
|
||||||
|
echo "ERROR: Guardrails failed. Only fix, lib, release, run, validate directories are allowed under scripts/." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#missing_files[@]}" -gt 0 ]; then
|
||||||
|
echo "### Missing script files" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
for m in "${missing_files[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
|
||||||
|
echo "ERROR: Guardrails failed. Missing required script files." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#legacy_glob_found[@]}" -gt 0 ]; then
|
||||||
|
echo "### Legacy validate_* scripts detected at scripts/ root (disallowed)" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
for m in "${legacy_glob_found[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
|
||||||
|
echo "ERROR: Guardrails failed. Move scripts into scripts/validate/ with approved filenames." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
non_exec=()
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -n "${f}" ] && non_exec+=("${f}")
|
||||||
|
done < <(find scripts -type f -name '*.sh' ! -perm -u=x 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ "${#non_exec[@]}" -gt 0 ]; then
|
||||||
|
echo "### Non-executable shell scripts detected" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
for m in "${non_exec[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
|
||||||
|
echo "ERROR: Guardrails failed. All scripts/**/*.sh must be executable." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sh_files="$(find scripts -type f -name '*.sh' 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [ -z "${sh_files}" ]; then
|
||||||
|
echo "No shell scripts found under scripts/." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
echo "Shell quality gate skipped." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -z "${f}" ] && continue
|
||||||
|
bash -n "${f}"
|
||||||
|
done <<< "${sh_files}"
|
||||||
|
|
||||||
|
shellcheck -x ${sh_files}
|
||||||
|
|
||||||
|
echo "Shell quality gate passed." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
|
||||||
repo_health:
|
repo_health:
|
||||||
name: Repository health
|
name: Repository health
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
needs: [access_check]
|
needs: [access_check]
|
||||||
if: ${{ needs.access_check.outputs.allowed == 'true' }}
|
if: ${{ needs.access_check.outputs.allowed == 'true' }}
|
||||||
permissions:
|
permissions:
|
||||||
@@ -471,6 +569,8 @@ jobs:
|
|||||||
"CHANGELOG.md"
|
"CHANGELOG.md"
|
||||||
"CONTRIBUTING.md"
|
"CONTRIBUTING.md"
|
||||||
"CODE_OF_CONDUCT.md"
|
"CODE_OF_CONDUCT.md"
|
||||||
|
"TODO.md"
|
||||||
|
"docs/docs-index.md"
|
||||||
)
|
)
|
||||||
|
|
||||||
optional_files=(
|
optional_files=(
|
||||||
@@ -503,7 +603,6 @@ jobs:
|
|||||||
[ ! -d "${p}" ] && missing_required+=("${p}/")
|
[ ! -d "${p}" ] && missing_required+=("${p}/")
|
||||||
done
|
done
|
||||||
|
|
||||||
# dev/ is the only source root. src/ must not exist.
|
|
||||||
if [ -d "src" ]; then
|
if [ -d "src" ]; then
|
||||||
missing_required+=("src/ (disallowed, use dev/ only)")
|
missing_required+=("src/ (disallowed, use dev/ only)")
|
||||||
fi
|
fi
|
||||||
@@ -544,20 +643,29 @@ jobs:
|
|||||||
content_warnings+=("README.md missing expected brand keyword")
|
content_warnings+=("README.md missing expected brand keyword")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export MISSING_REQUIRED="$(printf '%s\n' "${missing_required[@]:-}")"
|
export MISSING_REQUIRED="$(printf '%s
|
||||||
export MISSING_OPTIONAL="$(printf '%s\n' "${missing_optional[@]:-}")"
|
' "${missing_required[@]:-}")"
|
||||||
export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")"
|
export MISSING_OPTIONAL="$(printf '%s
|
||||||
|
' "${missing_optional[@]:-}")"
|
||||||
|
export CONTENT_WARNINGS="$(printf '%s
|
||||||
|
' "${content_warnings[@]:-}")"
|
||||||
|
|
||||||
report_json="$(python3 - <<'PY'
|
report_json="$(python3 - <<'PY'
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
profile = os.environ.get('PROFILE_RAW') or 'all'
|
profile = os.environ.get('PROFILE_RAW') or 'all'
|
||||||
required_files = ["README.md","LICENSE","CHANGELOG.md","CONTRIBUTING.md","CODE_OF_CONDUCT.md"]
|
required_files = ["README.md","LICENSE","CHANGELOG.md","CONTRIBUTING.md","CODE_OF_CONDUCT.md","TODO.md","docs/docs-index.md"]
|
||||||
optional_files = ["SECURITY.md","GOVERNANCE.md",".editorconfig",".gitattributes",".gitignore"]
|
optional_files = ["SECURITY.md","GOVERNANCE.md",".editorconfig",".gitattributes",".gitignore"]
|
||||||
required_paths = [".github/workflows","scripts","docs","dev"]
|
required_paths = [".github/workflows","scripts","docs","dev"]
|
||||||
missing_required = os.environ.get('MISSING_REQUIRED','').split('\n') if os.environ.get('MISSING_REQUIRED') else []
|
|
||||||
missing_optional = os.environ.get('MISSING_OPTIONAL','').split('\n') if os.environ.get('MISSING_OPTIONAL') else []
|
missing_required = os.environ.get('MISSING_REQUIRED','').split('
|
||||||
content_warnings = os.environ.get('CONTENT_WARNINGS','').split('\n') if os.environ.get('CONTENT_WARNINGS') else []
|
') if os.environ.get('MISSING_REQUIRED') else []
|
||||||
|
missing_optional = os.environ.get('MISSING_OPTIONAL','').split('
|
||||||
|
') if os.environ.get('MISSING_OPTIONAL') else []
|
||||||
|
content_warnings = os.environ.get('CONTENT_WARNINGS','').split('
|
||||||
|
') if os.environ.get('CONTENT_WARNINGS') else []
|
||||||
|
|
||||||
out = {
|
out = {
|
||||||
"profile": profile,
|
"profile": profile,
|
||||||
"checked": {
|
"checked": {
|
||||||
@@ -569,6 +677,7 @@ jobs:
|
|||||||
"missing_optional": [x for x in missing_optional if x],
|
"missing_optional": [x for x in missing_optional if x],
|
||||||
"content_warnings": [x for x in content_warnings if x],
|
"content_warnings": [x for x in content_warnings if x],
|
||||||
}
|
}
|
||||||
|
|
||||||
print(json.dumps(out, indent=2))
|
print(json.dumps(out, indent=2))
|
||||||
PY
|
PY
|
||||||
)"
|
)"
|
||||||
@@ -598,3 +707,29 @@ jobs:
|
|||||||
echo "### Repo content warnings" >> "${GITHUB_STEP_SUMMARY}"
|
echo "### Repo content warnings" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
for m in "${content_warnings[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
|
for m in "${content_warnings[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "### Guardrails: repository health"
|
||||||
|
echo ""
|
||||||
|
echo "### Guardrails report (JSON)"
|
||||||
|
echo "```json"
|
||||||
|
echo "${report_json}"
|
||||||
|
echo "```"
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
|
||||||
|
if [ "${#missing_required[@]}" -gt 0 ]; then
|
||||||
|
echo "### Missing required repo artifacts" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
for m in "${missing_required[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
|
||||||
|
echo "ERROR: Guardrails failed. Missing required repository artifacts." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#missing_optional[@]}" -gt 0 ]; then
|
||||||
|
echo "### Missing optional repo artifacts" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
for m in "${missing_optional[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#content_warnings[@]}" -gt 0 ]; then
|
||||||
|
echo "### Repo content warnings" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
for m in "${content_warnings[@]}"; do echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"; done
|
||||||
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user