Update repo_health.yml
This commit is contained in:
332
.github/workflows/repo_health.yml
vendored
332
.github/workflows/repo_health.yml
vendored
@@ -76,6 +76,9 @@ env:
|
|||||||
REPO_DISALLOWED_DIRS: src
|
REPO_DISALLOWED_DIRS: src
|
||||||
REPO_DISALLOWED_FILES: TODO.md,todo.md
|
REPO_DISALLOWED_FILES: TODO.md,todo.md
|
||||||
|
|
||||||
|
# Extended checks toggles
|
||||||
|
EXTENDED_CHECKS: "true"
|
||||||
|
|
||||||
# Operational toggles
|
# Operational toggles
|
||||||
SFTP_VERBOSE: "false"
|
SFTP_VERBOSE: "false"
|
||||||
|
|
||||||
@@ -167,7 +170,13 @@ jobs:
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
if [ "${profile}" = 'scripts' ] || [ "${profile}" = 'repo' ]; then
|
if [ "${profile}" = 'scripts' ] || [ "${profile}" = 'repo' ]; then
|
||||||
printf '%s\n' "Profile ${profile} selected. Skipping release configuration checks." >> "${GITHUB_STEP_SUMMARY}"
|
{
|
||||||
|
printf '%s\n' '### Release configuration'
|
||||||
|
printf '%s\n' "Profile: ${profile}"
|
||||||
|
printf '%s\n' 'Status: SKIPPED'
|
||||||
|
printf '%s\n' 'Reason: profile excludes release validation'
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -197,12 +206,39 @@ jobs:
|
|||||||
[ "${ok}" = false ] && missing+=("FTP_PROTOCOL_INVALID")
|
[ "${ok}" = false ] && missing+=("FTP_PROTOCOL_INVALID")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
target_path="${FTP_PATH:-}"
|
||||||
|
if [ -n "${FTP_PATH_SUFFIX:-}" ]; then
|
||||||
|
target_path="${target_path%/}/${FTP_PATH_SUFFIX#/}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
auth_method='none'
|
||||||
|
[ -n "${FTP_KEY:-}" ] && auth_method='key'
|
||||||
|
[ -z "${FTP_KEY:-}" ] && [ -n "${FTP_PASSWORD:-}" ] && auth_method='password'
|
||||||
|
|
||||||
|
{
|
||||||
|
printf '%s\n' '### Release configuration'
|
||||||
|
printf '%s\n' "Profile: ${profile}"
|
||||||
|
printf '%s\n' '| Control | Value |'
|
||||||
|
printf '%s\n' '|---|---|'
|
||||||
|
printf '%s\n' "| Protocol | ${proto} |"
|
||||||
|
printf '%s\n' "| Port | ${FTP_PORT:-22} |"
|
||||||
|
printf '%s\n' "| Auth | ${auth_method} |"
|
||||||
|
printf '%s\n' "| Path (resolved) | ${target_path} |"
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
|
||||||
if [ "${#missing_optional[@]}" -gt 0 ]; then
|
if [ "${#missing_optional[@]}" -gt 0 ]; then
|
||||||
{
|
{
|
||||||
printf '%s\n' '### Missing optional release configuration'
|
printf '%s\n' '### Missing optional release configuration'
|
||||||
for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done
|
for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done
|
||||||
printf '\n'
|
printf '\n'
|
||||||
} >> "${GITHUB_STEP_SUMMARY}"
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf '%s\n' '### Optional release configuration'
|
||||||
|
printf '%s\n' 'None missing.'
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${#missing[@]}" -gt 0 ]; then
|
if [ "${#missing[@]}" -gt 0 ]; then
|
||||||
@@ -215,7 +251,8 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
{
|
{
|
||||||
printf '%s\n' '### Guardrails release configuration'
|
printf '%s\n' '### Release configuration result'
|
||||||
|
printf '%s\n' 'Status: OK'
|
||||||
printf '%s\n' 'All required release variables present.'
|
printf '%s\n' 'All required release variables present.'
|
||||||
printf '\n'
|
printf '\n'
|
||||||
} >> "${GITHUB_STEP_SUMMARY}"
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
@@ -243,7 +280,13 @@ jobs:
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
if [ "${profile}" = 'scripts' ] || [ "${profile}" = 'repo' ]; then
|
if [ "${profile}" = 'scripts' ] || [ "${profile}" = 'repo' ]; then
|
||||||
printf '%s\n' "Profile ${profile} selected. Skipping SFTP connectivity check." >> "${GITHUB_STEP_SUMMARY}"
|
{
|
||||||
|
printf '%s\n' '### SFTP connectivity'
|
||||||
|
printf '%s\n' "Profile: ${profile}"
|
||||||
|
printf '%s\n' 'Status: SKIPPED'
|
||||||
|
printf '%s\n' 'Reason: profile excludes release validation'
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -258,10 +301,22 @@ jobs:
|
|||||||
sftp_v_opt=()
|
sftp_v_opt=()
|
||||||
[ "${sftp_verbose}" = 'true' ] && sftp_v_opt=(-vv)
|
[ "${sftp_verbose}" = 'true' ] && sftp_v_opt=(-vv)
|
||||||
|
|
||||||
|
auth_method='none'
|
||||||
|
if [ -n "${FTP_KEY:-}" ]; then
|
||||||
|
auth_method='key'
|
||||||
|
elif [ -n "${FTP_PASSWORD:-}" ]; then
|
||||||
|
auth_method='password'
|
||||||
|
fi
|
||||||
|
|
||||||
{
|
{
|
||||||
printf '%s\n' '### SFTP connectivity test'
|
printf '%s\n' '### SFTP connectivity'
|
||||||
printf '%s\n' "Target path: ${target_path}"
|
printf '%s\n' '| Control | Value |'
|
||||||
printf '%s\n' 'Attempting non-destructive SFTP session'
|
printf '%s\n' '|---|---|'
|
||||||
|
printf '%s\n' "| Host | ${FTP_HOST} |"
|
||||||
|
printf '%s\n' "| User | ${FTP_USER} |"
|
||||||
|
printf '%s\n' "| Port | ${port} |"
|
||||||
|
printf '%s\n' "| Auth | ${auth_method} |"
|
||||||
|
printf '%s\n' "| Path (resolved) | ${target_path} |"
|
||||||
printf '\n'
|
printf '\n'
|
||||||
} >> "${GITHUB_STEP_SUMMARY}"
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
|
||||||
@@ -277,7 +332,12 @@ jobs:
|
|||||||
if [ -n "${FTP_PASSWORD:-}" ]; then
|
if [ -n "${FTP_PASSWORD:-}" ]; then
|
||||||
first_line="$(head -n 1 "${key_file}" || true)"
|
first_line="$(head -n 1 "${key_file}" || true)"
|
||||||
if printf '%s\n' "${first_line}" | grep -q '^PuTTY-User-Key-File-'; then
|
if printf '%s\n' "${first_line}" | grep -q '^PuTTY-User-Key-File-'; then
|
||||||
printf '%s\n' 'ERROR: FTP_KEY appears to be a PuTTY PPK. Provide an OpenSSH private key.' >> "${GITHUB_STEP_SUMMARY}"
|
{
|
||||||
|
printf '%s\n' '### SFTP connectivity result'
|
||||||
|
printf '%s\n' 'Status: FAILED'
|
||||||
|
printf '%s\n' 'Reason: FTP_KEY appears to be a PuTTY PPK. Provide an OpenSSH private key.'
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
ssh-keygen -p -P "${FTP_PASSWORD}" -N '' -f "${key_file}" >/dev/null
|
ssh-keygen -p -P "${FTP_PASSWORD}" -N '' -f "${key_file}" >/dev/null
|
||||||
@@ -290,24 +350,31 @@ jobs:
|
|||||||
printf '%s' "${sftp_cmds}" | sshpass -p "${FTP_PASSWORD}" sftp "${sftp_v_opt[@]}" -oBatchMode=no -oStrictHostKeyChecking=no -P "${port}" "${FTP_USER}@${FTP_HOST}" >/tmp/sftp_check.log 2>&1
|
printf '%s' "${sftp_cmds}" | sshpass -p "${FTP_PASSWORD}" sftp "${sftp_v_opt[@]}" -oBatchMode=no -oStrictHostKeyChecking=no -P "${port}" "${FTP_USER}@${FTP_HOST}" >/tmp/sftp_check.log 2>&1
|
||||||
sftp_rc=$?
|
sftp_rc=$?
|
||||||
else
|
else
|
||||||
printf '%s\n' 'ERROR: No FTP_KEY or FTP_PASSWORD provided for SFTP authentication.' >> "${GITHUB_STEP_SUMMARY}"
|
{
|
||||||
|
printf '%s\n' '### SFTP connectivity result'
|
||||||
|
printf '%s\n' 'Status: FAILED'
|
||||||
|
printf '%s\n' 'Reason: No FTP_KEY or FTP_PASSWORD provided for SFTP authentication.'
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
printf '%s\n' '### SFTP connectivity result' >> "${GITHUB_STEP_SUMMARY}"
|
|
||||||
if [ "${sftp_rc}" -eq 0 ]; then
|
|
||||||
printf '%s\n' 'Status: SUCCESS' >> "${GITHUB_STEP_SUMMARY}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
{
|
{
|
||||||
printf '%s\n' "Status: FAILED (exit code ${sftp_rc})"
|
printf '%s\n' '### SFTP connectivity result'
|
||||||
|
if [ "${sftp_rc}" -eq 0 ]; then
|
||||||
|
printf '%s\n' 'Status: SUCCESS'
|
||||||
|
printf '%s\n' 'Validated host connectivity and remote path access.'
|
||||||
|
else
|
||||||
|
printf '%s\n' "Status: FAILED (exit code ${sftp_rc})"
|
||||||
|
printf '\n'
|
||||||
|
printf '%s\n' 'Last SFTP output'
|
||||||
|
tail -n 60 /tmp/sftp_check.log || true
|
||||||
|
fi
|
||||||
printf '\n'
|
printf '\n'
|
||||||
printf '%s\n' 'Last SFTP output'
|
|
||||||
tail -n 60 /tmp/sftp_check.log || true
|
|
||||||
} >> "${GITHUB_STEP_SUMMARY}"
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
exit 1
|
|
||||||
|
[ "${sftp_rc}" -eq 0 ] || exit 1
|
||||||
|
|
||||||
scripts_governance:
|
scripts_governance:
|
||||||
name: Scripts governance
|
name: Scripts governance
|
||||||
@@ -340,14 +407,21 @@ jobs:
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
if [ "${profile}" = 'release' ] || [ "${profile}" = 'repo' ]; then
|
if [ "${profile}" = 'release' ] || [ "${profile}" = 'repo' ]; then
|
||||||
printf '%s\n' "Profile ${profile} selected. Skipping scripts governance." >> "${GITHUB_STEP_SUMMARY}"
|
{
|
||||||
|
printf '%s\n' '### Scripts governance'
|
||||||
|
printf '%s\n' "Profile: ${profile}"
|
||||||
|
printf '%s\n' 'Status: SKIPPED'
|
||||||
|
printf '%s\n' 'Reason: profile excludes scripts governance'
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d scripts ]; then
|
if [ ! -d scripts ]; then
|
||||||
{
|
{
|
||||||
printf '%s\n' '### Scripts governance'
|
printf '%s\n' '### Scripts governance'
|
||||||
printf '%s\n' 'Warning: scripts/ directory not present. No scripts governance enforced.'
|
printf '%s\n' 'Status: OK (advisory)'
|
||||||
|
printf '%s\n' 'scripts/ directory not present. No scripts governance enforced.'
|
||||||
printf '\n'
|
printf '\n'
|
||||||
} >> "${GITHUB_STEP_SUMMARY}"
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
exit 0
|
exit 0
|
||||||
@@ -375,34 +449,45 @@ jobs:
|
|||||||
|
|
||||||
{
|
{
|
||||||
printf '%s\n' '### Scripts governance'
|
printf '%s\n' '### Scripts governance'
|
||||||
|
printf '%s\n' "Profile: ${profile}"
|
||||||
|
printf '%s\n' '| Area | Status | Notes |'
|
||||||
|
printf '%s\n' '|---|---|---|'
|
||||||
|
|
||||||
|
if [ "${#missing_dirs[@]}" -gt 0 ]; then
|
||||||
|
printf '%s\n' '| Required directories | Warning | Missing required subfolders |'
|
||||||
|
else
|
||||||
|
printf '%s\n' '| Required directories | OK | All required subfolders present |'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
|
||||||
|
printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |'
|
||||||
|
else
|
||||||
|
printf '%s\n' '| Directory policy | OK | No unapproved directories |'
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' '| Enforcement mode | Advisory | scripts folder is optional |'
|
||||||
|
printf '\n'
|
||||||
|
|
||||||
if [ "${#missing_dirs[@]}" -gt 0 ]; then
|
if [ "${#missing_dirs[@]}" -gt 0 ]; then
|
||||||
printf '%s\n' 'Missing required script directories:'
|
printf '%s\n' 'Missing required script directories:'
|
||||||
for m in "${missing_dirs[@]}"; do printf '%s\n' "- ${m}"; done
|
for m in "${missing_dirs[@]}"; do printf '%s\n' "- ${m}"; done
|
||||||
printf '\n'
|
printf '\n'
|
||||||
|
else
|
||||||
|
printf '%s\n' 'Missing required script directories: none.'
|
||||||
|
printf '\n'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
|
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
|
||||||
printf '%s\n' 'Unapproved script directories detected:'
|
printf '%s\n' 'Unapproved script directories detected:'
|
||||||
for m in "${unapproved_dirs[@]}"; do printf '%s\n' "- ${m}"; done
|
for m in "${unapproved_dirs[@]}"; do printf '%s\n' "- ${m}"; done
|
||||||
printf '\n'
|
printf '\n'
|
||||||
|
else
|
||||||
|
printf '%s\n' 'Unapproved script directories detected: none.'
|
||||||
|
printf '\n'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf '%s\n' '| Area | Status | Notes |'
|
|
||||||
printf '%s\n' '|------|--------|-------|'
|
|
||||||
if [ "${#missing_dirs[@]}" -gt 0 ]; then
|
|
||||||
printf '%s\n' '| Required directories | Warning | Missing required subfolders |'
|
|
||||||
else
|
|
||||||
printf '%s\n' '| Required directories | OK | All required subfolders present |'
|
|
||||||
fi
|
|
||||||
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
|
|
||||||
printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |'
|
|
||||||
else
|
|
||||||
printf '%s\n' '| Directory policy | OK | No unapproved directories |'
|
|
||||||
fi
|
|
||||||
printf '%s\n' '| Enforcement mode | Advisory | scripts folder is optional |'
|
|
||||||
printf '\n'
|
|
||||||
printf '%s\n' 'Scripts governance completed in advisory mode.'
|
printf '%s\n' 'Scripts governance completed in advisory mode.'
|
||||||
|
printf '\n'
|
||||||
} >> "${GITHUB_STEP_SUMMARY}"
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
|
||||||
repo_health:
|
repo_health:
|
||||||
@@ -410,7 +495,7 @@ jobs:
|
|||||||
needs: access_check
|
needs: access_check
|
||||||
if: ${{ needs.access_check.outputs.allowed == 'true' }}
|
if: ${{ needs.access_check.outputs.allowed == 'true' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 20
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
@@ -436,7 +521,13 @@ jobs:
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
if [ "${profile}" = 'release' ] || [ "${profile}" = 'scripts' ]; then
|
if [ "${profile}" = 'release' ] || [ "${profile}" = 'scripts' ]; then
|
||||||
printf '%s\n' "Profile ${profile} selected. Skipping repository health checks." >> "${GITHUB_STEP_SUMMARY}"
|
{
|
||||||
|
printf '%s\n' '### Repository health'
|
||||||
|
printf '%s\n' "Profile: ${profile}"
|
||||||
|
printf '%s\n' 'Status: SKIPPED'
|
||||||
|
printf '%s\n' 'Reason: profile excludes repository health'
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -498,6 +589,10 @@ jobs:
|
|||||||
content_warnings+=("CHANGELOG.md missing '# Changelog' header")
|
content_warnings+=("CHANGELOG.md missing '# Changelog' header")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -f 'CHANGELOG.md' ] && grep -Eq '^[# ]*Unreleased' CHANGELOG.md; then
|
||||||
|
content_warnings+=("CHANGELOG.md contains Unreleased section (review release readiness)")
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -f 'LICENSE' ] && ! grep -qiE 'GNU GENERAL PUBLIC LICENSE|GPL' LICENSE; then
|
if [ -f 'LICENSE' ] && ! grep -qiE 'GNU GENERAL PUBLIC LICENSE|GPL' LICENSE; then
|
||||||
content_warnings+=("LICENSE does not look like a GPL text")
|
content_warnings+=("LICENSE does not look like a GPL text")
|
||||||
fi
|
fi
|
||||||
@@ -533,8 +628,15 @@ jobs:
|
|||||||
)"
|
)"
|
||||||
|
|
||||||
{
|
{
|
||||||
printf '%s\n' '### Guardrails repository health'
|
printf '%s\n' '### Repository health'
|
||||||
|
printf '%s\n' "Profile: ${profile}"
|
||||||
|
printf '%s\n' '| Metric | Value |'
|
||||||
|
printf '%s\n' '|---|---|'
|
||||||
|
printf '%s\n' "| Missing required | ${#missing_required[@]} |"
|
||||||
|
printf '%s\n' "| Missing optional | ${#missing_optional[@]} |"
|
||||||
|
printf '%s\n' "| Content warnings | ${#content_warnings[@]} |"
|
||||||
printf '\n'
|
printf '\n'
|
||||||
|
|
||||||
printf '%s\n' '### Guardrails report (JSON)'
|
printf '%s\n' '### Guardrails report (JSON)'
|
||||||
printf '%s\n' '```json'
|
printf '%s\n' '```json'
|
||||||
printf '%s\n' "${report_json}"
|
printf '%s\n' "${report_json}"
|
||||||
@@ -547,6 +649,7 @@ jobs:
|
|||||||
printf '%s\n' '### Missing required repo artifacts'
|
printf '%s\n' '### Missing required repo artifacts'
|
||||||
for m in "${missing_required[@]}"; do printf '%s\n' "- ${m}"; done
|
for m in "${missing_required[@]}"; do printf '%s\n' "- ${m}"; done
|
||||||
printf '%s\n' 'ERROR: Guardrails failed. Missing required repository artifacts.'
|
printf '%s\n' 'ERROR: Guardrails failed. Missing required repository artifacts.'
|
||||||
|
printf '\n'
|
||||||
} >> "${GITHUB_STEP_SUMMARY}"
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -567,6 +670,157 @@ jobs:
|
|||||||
} >> "${GITHUB_STEP_SUMMARY}"
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}"
|
extended_enabled="${EXTENDED_CHECKS:-true}"
|
||||||
|
extended_findings=()
|
||||||
|
|
||||||
# EOF
|
if [ "${extended_enabled}" = 'true' ]; then
|
||||||
|
# CODEOWNERS presence
|
||||||
|
if [ -f '.github/CODEOWNERS' ] || [ -f 'CODEOWNERS' ] || [ -f 'docs/CODEOWNERS' ]; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
extended_findings+=("CODEOWNERS not found (.github/CODEOWNERS preferred)")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Workflow pinning advisory: flag uses @main/@master
|
||||||
|
if ls .github/workflows/*.yml >/dev/null 2>&1; then
|
||||||
|
bad_refs="$(grep -RIn --include='*.yml' --include='*.yaml' -E '^[[:space:]]*uses:[[:space:]]*[^#]+@(main|master)\b' .github/workflows 2>/dev/null || true)"
|
||||||
|
if [ -n "${bad_refs}" ]; then
|
||||||
|
extended_findings+=("Workflows reference actions @main/@master (pin versions): see log excerpt")
|
||||||
|
{
|
||||||
|
printf '%s\n' '### Workflow pinning advisory'
|
||||||
|
printf '%s\n' 'Found uses: entries pinned to main/master:'
|
||||||
|
printf '%s\n' '```'
|
||||||
|
printf '%s\n' "${bad_refs}"
|
||||||
|
printf '%s\n' '```'
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Docs index link integrity (docs/docs-index.md)
|
||||||
|
if [ -f 'docs/docs-index.md' ]; then
|
||||||
|
missing_links="$(python3 - <<'PY'
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
idx = 'docs/docs-index.md'
|
||||||
|
base = os.getcwd()
|
||||||
|
|
||||||
|
bad = []
|
||||||
|
pat = re.compile(r'\[[^\]]+\]\(([^)]+)\)')
|
||||||
|
|
||||||
|
with open(idx, 'r', encoding='utf-8') as f:
|
||||||
|
for line in f:
|
||||||
|
for m in pat.findall(line):
|
||||||
|
link = m.strip()
|
||||||
|
if link.startswith('http://') or link.startswith('https://') or link.startswith('#') or link.startswith('mailto:'):
|
||||||
|
continue
|
||||||
|
if link.startswith('/'):
|
||||||
|
rel = link.lstrip('/')
|
||||||
|
else:
|
||||||
|
rel = os.path.normpath(os.path.join(os.path.dirname(idx), link))
|
||||||
|
rel = rel.split('#', 1)[0]
|
||||||
|
rel = rel.split('?', 1)[0]
|
||||||
|
if not rel:
|
||||||
|
continue
|
||||||
|
p = os.path.join(base, rel)
|
||||||
|
if not os.path.exists(p):
|
||||||
|
bad.append(rel)
|
||||||
|
|
||||||
|
print('\n'.join(sorted(set(bad))))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
if [ -n "${missing_links}" ]; then
|
||||||
|
extended_findings+=("docs/docs-index.md contains broken relative links")
|
||||||
|
{
|
||||||
|
printf '%s\n' '### Docs index link integrity'
|
||||||
|
printf '%s\n' 'Broken relative links:'
|
||||||
|
while IFS= read -r l; do [ -n "${l}" ] && printf '%s\n' "- ${l}"; done <<< "${missing_links}"
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ShellCheck advisory
|
||||||
|
if [ -d 'scripts' ]; then
|
||||||
|
if ! command -v shellcheck >/dev/null 2>&1; then
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y shellcheck >/dev/null
|
||||||
|
fi
|
||||||
|
sc_out="$(shellcheck -S warning -x scripts/**/*.sh 2>/dev/null || true)"
|
||||||
|
if [ -n "${sc_out}" ]; then
|
||||||
|
extended_findings+=("ShellCheck warnings detected (advisory)")
|
||||||
|
{
|
||||||
|
printf '%s\n' '### ShellCheck (advisory)'
|
||||||
|
printf '%s\n' '```'
|
||||||
|
printf '%s\n' "${sc_out}" | head -n 200
|
||||||
|
printf '%s\n' '```'
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# SPDX header advisory for common source types
|
||||||
|
spdx_missing=()
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -z "${f}" ] && continue
|
||||||
|
if ! head -n 40 "${f}" | grep -q 'SPDX-License-Identifier:'; then
|
||||||
|
spdx_missing+=("${f}")
|
||||||
|
fi
|
||||||
|
done < <(git ls-files '*.sh' '*.php' '*.js' '*.ts' '*.css' '*.xml' '*.yml' '*.yaml' 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ "${#spdx_missing[@]}" -gt 0 ]; then
|
||||||
|
extended_findings+=("SPDX header missing in some tracked files (advisory)")
|
||||||
|
{
|
||||||
|
printf '%s\n' '### SPDX header advisory'
|
||||||
|
printf '%s\n' 'Files missing SPDX-License-Identifier (first 40 lines scan):'
|
||||||
|
for f in "${spdx_missing[@]}"; do printf '%s\n' "- ${f}"; done
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Git hygiene advisory: branches older than 180 days (remote)
|
||||||
|
stale_cutoff_days=180
|
||||||
|
stale_branches="$(git for-each-ref --format='%(refname:short) %(committerdate:unix)' refs/remotes/origin 2>/dev/null | awk -v now="$(date +%s)" -v days="${stale_cutoff_days}" '{if (now-$2 > days*86400) print $1}' | sed 's#^origin/##' | grep -v '^HEAD$' | head -n 50 || true)"
|
||||||
|
if [ -n "${stale_branches}" ]; then
|
||||||
|
extended_findings+=("Stale remote branches detected (advisory)")
|
||||||
|
{
|
||||||
|
printf '%s\n' '### Git hygiene advisory'
|
||||||
|
printf '%s\n' "Branches with last commit older than ${stale_cutoff_days} days (sample up to 50):"
|
||||||
|
while IFS= read -r b; do [ -n "${b}" ] && printf '%s\n' "- ${b}"; done <<< "${stale_branches}"
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
printf '%s\n' '### Guardrails coverage matrix'
|
||||||
|
printf '%s\n' '| Domain | Status | Notes |'
|
||||||
|
printf '%s\n' '|---|---|---|'
|
||||||
|
printf '%s\n' '| Access control | OK | Admin-only execution gate |'
|
||||||
|
printf '%s\n' '| Release configuration | OK | Variable presence and protocol gate |'
|
||||||
|
printf '%s\n' '| SFTP connectivity | OK | Connectivity plus remote path resolution |'
|
||||||
|
printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |'
|
||||||
|
printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |'
|
||||||
|
printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |'
|
||||||
|
if [ "${extended_enabled}" = 'true' ]; then
|
||||||
|
if [ "${#extended_findings[@]}" -gt 0 ]; then
|
||||||
|
printf '%s\n' '| Extended checks | Warning | See extended findings below |'
|
||||||
|
else
|
||||||
|
printf '%s\n' '| Extended checks | OK | No findings |'
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf '%s\n' '| Extended checks | SKIPPED | EXTENDED_CHECKS disabled |'
|
||||||
|
fi
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
|
||||||
|
if [ "${extended_enabled}" = 'true' ] && [ "${#extended_findings[@]}" -gt 0 ]; then
|
||||||
|
{
|
||||||
|
printf '%s\n' '### Extended findings (advisory)'
|
||||||
|
for f in "${extended_findings[@]}"; do printf '%s\n' "- ${f}"; done
|
||||||
|
printf '\n'
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
|||||||
Reference in New Issue
Block a user