Update repo_health.yml
This commit is contained in:
275
.github/workflows/repo_health.yml
vendored
275
.github/workflows/repo_health.yml
vendored
@@ -29,6 +29,14 @@
|
||||
|
||||
name: Joomla Repo Health
|
||||
|
||||
concurrency:
|
||||
group: repo-health-${{ github.repository }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -54,6 +62,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
access_check:
|
||||
timeout-minutes: 10
|
||||
name: Access control
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -104,6 +113,7 @@ jobs:
|
||||
exit 1
|
||||
|
||||
release_config:
|
||||
timeout-minutes: 20
|
||||
name: Release configuration
|
||||
runs-on: ubuntu-latest
|
||||
needs: [access_check]
|
||||
@@ -206,7 +216,7 @@ jobs:
|
||||
|
||||
mkdir -p "$HOME/.ssh"
|
||||
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}"
|
||||
|
||||
# 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 "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:
|
||||
name: Scripts and tooling
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: [access_check]
|
||||
if: ${{ needs.access_check.outputs.allowed == 'true' }}
|
||||
permissions:
|
||||
@@ -325,49 +347,124 @@ jobs:
|
||||
command -v xmllint >/dev/null 2>&1 && tool_status+=("xmllint") || true
|
||||
command -v shellcheck >/dev/null 2>&1 && tool_status+=("shellcheck") || true
|
||||
|
||||
export MISSING_DIRS="$(printf '%s\n' "${missing_dirs[@]:-}")"
|
||||
export MISSING_FILES="$(printf '%s\n' "${missing_files[@]:-}")"
|
||||
export MISSING_DIRS="$(printf '%s
|
||||
' "${missing_dirs[@]:-}")"
|
||||
export MISSING_FILES="$(printf '%s
|
||||
' "${missing_files[@]:-}")"
|
||||
export TOOLS="${tool_status[*]:-}"
|
||||
|
||||
report_json="$(python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
profile = os.environ.get('PROFILE_RAW') or 'all'
|
||||
required_script_dirs = [
|
||||
"scripts/fix",
|
||||
"scripts/lib",
|
||||
"scripts/release",
|
||||
"scripts/run",
|
||||
"scripts/validate",
|
||||
]
|
||||
required_script_files = [
|
||||
"scripts/validate/manifest.sh",
|
||||
"scripts/validate/xml_wellformed.sh",
|
||||
"scripts/validate/changelog.sh",
|
||||
"scripts/validate/tabs.sh",
|
||||
"scripts/validate/paths.sh",
|
||||
"scripts/validate/version_alignment.sh",
|
||||
"scripts/validate/language_structure.sh",
|
||||
"scripts/validate/php_syntax.sh",
|
||||
"scripts/validate/no_secrets.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 []
|
||||
tools = os.environ.get('TOOLS','').split() if os.environ.get('TOOLS') else []
|
||||
out = {
|
||||
"profile": profile,
|
||||
"checked": {
|
||||
"required_script_dirs": required_script_dirs,
|
||||
"required_script_files": required_script_files,
|
||||
},
|
||||
"missing_dirs": [x for x in missing_dirs if x],
|
||||
"missing_files": [x for x in missing_files if x],
|
||||
"tools_available": tools,
|
||||
}
|
||||
print(json.dumps(out, indent=2))
|
||||
PY
|
||||
)"
|
||||
import json
|
||||
import os
|
||||
|
||||
profile = os.environ.get('PROFILE_RAW') or 'all'
|
||||
required_script_dirs = [
|
||||
"scripts/fix",
|
||||
"scripts/lib",
|
||||
"scripts/release",
|
||||
"scripts/run",
|
||||
"scripts/validate",
|
||||
]
|
||||
required_script_files = [
|
||||
"scripts/validate/manifest.sh",
|
||||
"scripts/validate/xml_wellformed.sh",
|
||||
"scripts/validate/changelog.sh",
|
||||
"scripts/validate/tabs.sh",
|
||||
"scripts/validate/paths.sh",
|
||||
"scripts/validate/version_alignment.sh",
|
||||
"scripts/validate/language_structure.sh",
|
||||
"scripts/validate/php_syntax.sh",
|
||||
"scripts/validate/no_secrets.sh",
|
||||
"scripts/validate/license_headers.sh",
|
||||
]
|
||||
|
||||
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 []
|
||||
|
||||
out = {
|
||||
"profile": profile,
|
||||
"checked": {
|
||||
"required_script_dirs": required_script_dirs,
|
||||
"required_script_files": required_script_files,
|
||||
},
|
||||
"missing_dirs": [x for x in missing_dirs if x],
|
||||
"missing_files": [x for x in missing_files if x],
|
||||
"tools_available": tools,
|
||||
}
|
||||
|
||||
print(json.dumps(out, indent=2))
|
||||
PY
|
||||
)"
|
||||
|
||||
{
|
||||
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}"
|
||||
|
||||
{
|
||||
echo "### Guardrails: scripts and tooling"
|
||||
@@ -439,6 +536,7 @@ jobs:
|
||||
repo_health:
|
||||
name: Repository health
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
needs: [access_check]
|
||||
if: ${{ needs.access_check.outputs.allowed == 'true' }}
|
||||
permissions:
|
||||
@@ -471,6 +569,8 @@ jobs:
|
||||
"CHANGELOG.md"
|
||||
"CONTRIBUTING.md"
|
||||
"CODE_OF_CONDUCT.md"
|
||||
"TODO.md"
|
||||
"docs/docs-index.md"
|
||||
)
|
||||
|
||||
optional_files=(
|
||||
@@ -503,7 +603,6 @@ jobs:
|
||||
[ ! -d "${p}" ] && missing_required+=("${p}/")
|
||||
done
|
||||
|
||||
# dev/ is the only source root. src/ must not exist.
|
||||
if [ -d "src" ]; then
|
||||
missing_required+=("src/ (disallowed, use dev/ only)")
|
||||
fi
|
||||
@@ -544,34 +643,70 @@ jobs:
|
||||
content_warnings+=("README.md missing expected brand keyword")
|
||||
fi
|
||||
|
||||
export MISSING_REQUIRED="$(printf '%s\n' "${missing_required[@]:-}")"
|
||||
export MISSING_OPTIONAL="$(printf '%s\n' "${missing_optional[@]:-}")"
|
||||
export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")"
|
||||
export MISSING_REQUIRED="$(printf '%s
|
||||
' "${missing_required[@]:-}")"
|
||||
export MISSING_OPTIONAL="$(printf '%s
|
||||
' "${missing_optional[@]:-}")"
|
||||
export CONTENT_WARNINGS="$(printf '%s
|
||||
' "${content_warnings[@]:-}")"
|
||||
|
||||
report_json="$(python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
profile = os.environ.get('PROFILE_RAW') or 'all'
|
||||
required_files = ["README.md","LICENSE","CHANGELOG.md","CONTRIBUTING.md","CODE_OF_CONDUCT.md"]
|
||||
optional_files = ["SECURITY.md","GOVERNANCE.md",".editorconfig",".gitattributes",".gitignore"]
|
||||
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 []
|
||||
content_warnings = os.environ.get('CONTENT_WARNINGS','').split('\n') if os.environ.get('CONTENT_WARNINGS') else []
|
||||
out = {
|
||||
"profile": profile,
|
||||
"checked": {
|
||||
"required_files": required_files,
|
||||
"optional_files": optional_files,
|
||||
"required_paths": required_paths,
|
||||
},
|
||||
"missing_required": [x for x in missing_required if x],
|
||||
"missing_optional": [x for x in missing_optional if x],
|
||||
"content_warnings": [x for x in content_warnings if x],
|
||||
}
|
||||
print(json.dumps(out, indent=2))
|
||||
PY
|
||||
)"
|
||||
import json
|
||||
import os
|
||||
|
||||
profile = os.environ.get('PROFILE_RAW') or 'all'
|
||||
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"]
|
||||
required_paths = [".github/workflows","scripts","docs","dev"]
|
||||
|
||||
missing_required = os.environ.get('MISSING_REQUIRED','').split('
|
||||
') 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 = {
|
||||
"profile": profile,
|
||||
"checked": {
|
||||
"required_files": required_files,
|
||||
"optional_files": optional_files,
|
||||
"required_paths": required_paths,
|
||||
},
|
||||
"missing_required": [x for x in missing_required if x],
|
||||
"missing_optional": [x for x in missing_optional if x],
|
||||
"content_warnings": [x for x in content_warnings if x],
|
||||
}
|
||||
|
||||
print(json.dumps(out, indent=2))
|
||||
PY
|
||||
)"
|
||||
|
||||
{
|
||||
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
|
||||
|
||||
{
|
||||
echo "### Guardrails: repository health"
|
||||
|
||||
Reference in New Issue
Block a user