diff --git a/.github/workflows/techapi-pr-validation-comment.yml b/.github/workflows/techapi-pr-validation-comment.yml
index 03fcf49..b5e6217 100644
--- a/.github/workflows/techapi-pr-validation-comment.yml
+++ b/.github/workflows/techapi-pr-validation-comment.yml
@@ -64,7 +64,56 @@ jobs:
python-version: "3.12"
cache: pip
+ - name: Detect TechAPI homepage changes
+ id: site_changes
+ shell: bash
+ run: |
+ set -euo pipefail
+ python - <<'PY' >> "$GITHUB_OUTPUT"
+ from __future__ import annotations
+
+ import hashlib
+ import re
+ from pathlib import Path
+
+ def digest(path: Path) -> str:
+ return hashlib.sha256(path.read_bytes()).hexdigest()
+
+ def rel_site_files(root: Path) -> dict[str, Path]:
+ site = root / "site"
+ if not site.exists():
+ return {}
+ wanted_files = {"site/package.json", "site/package-lock.json"}
+ files: dict[str, Path] = {}
+ for path in sorted(site.rglob("*")):
+ if not path.is_file():
+ continue
+ rel = str(path.relative_to(root)).replace("\\", "/")
+ if (
+ rel.startswith("site/src/")
+ or rel.startswith("site/public/")
+ or rel in wanted_files
+ or re.match(r"site/astro\.config\.[cm]?[jt]s$", rel)
+ ):
+ if rel.startswith("site/public/v1/") or rel == "site/public/openapi.json":
+ continue
+ files[rel] = path
+ return files
+
+ head = rel_site_files(Path("TechAPI"))
+ base = rel_site_files(Path("TechAPI-main"))
+ added = sorted(set(head) - set(base))
+ deleted = sorted(set(base) - set(head))
+ modified = sorted(key for key in set(head) & set(base) if digest(head[key]) != digest(base[key]))
+ has_changes = bool(added or modified or deleted)
+ print(f"changed={'true' if has_changes else 'false'}")
+ print(f"added={len(added)}")
+ print(f"modified={len(modified)}")
+ print(f"deleted={len(deleted)}")
+ PY
+
- uses: actions/setup-node@v4
+ if: steps.site_changes.outputs.changed == 'true'
with:
node-version: "22"
cache: npm
@@ -105,6 +154,7 @@ jobs:
echo "integrity_status=${integrity_status:-1}" >> "$GITHUB_OUTPUT"
- name: Build TechAPI homepage
+ if: steps.site_changes.outputs.changed == 'true'
id: site_build
shell: bash
run: |
@@ -419,11 +469,13 @@ jobs:
run: |
short_sha="${TECHAPI_HEAD_SHA:0:7}"
result="PASS"
- if [ "${{ steps.validate.outputs.status }}" != "success" ] || [ "${{ steps.site_build.outputs.status }}" != "0" ]; then
+ site_changed="${{ steps.site_changes.outputs.changed }}"
+ site_build_status="${{ steps.site_build.outputs.status }}"
+ if [ "${{ steps.validate.outputs.status }}" != "success" ] || { [ "${site_changed}" = "true" ] && [ "${site_build_status}" != "0" ]; }; then
result="FAIL"
fi
- VALIDATION_STATUS="${{ steps.validate.outputs.status }}" SITE_BUILD_STATUS="${{ steps.site_build.outputs.status }}" python - <<'PY'
+ VALIDATION_STATUS="${{ steps.validate.outputs.status }}" SITE_CHANGED="${{ steps.site_changes.outputs.changed }}" SITE_BUILD_STATUS="${{ steps.site_build.outputs.status }}" python - <<'PY'
from __future__ import annotations
import os
@@ -434,7 +486,8 @@ jobs:
log = log_path.read_text(encoding="utf-8", errors="replace")
lines = log.splitlines()
failed = os.environ.get("VALIDATION_STATUS") != "success"
- site_failed = os.environ.get("SITE_BUILD_STATUS") != "0"
+ site_changed = os.environ.get("SITE_CHANGED") == "true"
+ site_failed = site_changed and os.environ.get("SITE_BUILD_STATUS") != "0"
section_counts: dict[str, int] = {}
current_section: str | None = None
@@ -491,28 +544,29 @@ jobs:
out.append("")
out.append("")
- site_log_path = Path("site-build.log")
- site_log = site_log_path.read_text(encoding="utf-8", errors="replace") if site_log_path.exists() else ""
- site_log = re.sub(r"\x1b\[[0-9;]*m", "", site_log)
- if site_failed:
- out.append("")
- out.append("Detailed homepage build log excerpt
")
- out.append("")
- out.append("```text")
- excerpt = site_log[-12000:] if len(site_log) > 12000 else site_log
- out.append(excerpt.rstrip())
- out.append("```")
- out.append("")
- out.append(" ")
- elif site_log:
- summary = [line for line in site_log.splitlines() if "Complete!" in line or "page(s) built" in line]
- if summary:
+ if site_changed:
+ site_log_path = Path("site-build.log")
+ site_log = site_log_path.read_text(encoding="utf-8", errors="replace") if site_log_path.exists() else ""
+ site_log = re.sub(r"\x1b\[[0-9;]*m", "", site_log)
+ if site_failed:
out.append("")
- out.append("Homepage build:")
+ out.append("Detailed homepage build log excerpt
")
out.append("")
out.append("```text")
- out.extend(summary[-4:])
+ excerpt = site_log[-12000:] if len(site_log) > 12000 else site_log
+ out.append(excerpt.rstrip())
out.append("```")
+ out.append("")
+ out.append(" ")
+ elif site_log:
+ summary = [line for line in site_log.splitlines() if "Complete!" in line or "page(s) built" in line]
+ if summary:
+ out.append("")
+ out.append("Homepage build:")
+ out.append("")
+ out.append("```text")
+ out.extend(summary[-4:])
+ out.append("```")
Path("validation-notes.md").write_text("\n".join(out) + "\n", encoding="utf-8")
PY
@@ -531,11 +585,15 @@ jobs:
echo "| --- | --- |"
echo "| \`python -m app.validate\` | $([ "${{ steps.validate.outputs.app_status }}" = "0" ] && echo PASS || echo FAIL) |"
echo "| \`python integrity_check.py TechAPI/data --strict\` | $([ "${{ steps.validate.outputs.integrity_status }}" = "0" ] && echo PASS || echo FAIL) |"
- echo "| \`cd TechAPI/site && npm ci && npm run build\` | $([ "${{ steps.site_build.outputs.status }}" = "0" ] && echo PASS || echo FAIL) |"
+ if [ "${site_changed}" = "true" ]; then
+ echo "| \`cd TechAPI/site && npm ci && npm run build\` | $([ "${site_build_status}" = "0" ] && echo PASS || echo FAIL) |"
+ fi
echo
cat change-review.md
- echo
- cat site-change-review.md
+ if [ "${site_changed}" = "true" ]; then
+ echo
+ cat site-change-review.md
+ fi
} > change-comment.md
{
@@ -586,5 +644,5 @@ jobs:
run: echo "::warning::TECHENGINEBOT_TOKEN/TECHAPI_TOKEN is not configured; validation ran but no PR comment was posted."
- name: Fail on validation errors
- if: steps.validate.outputs.status != 'success' || steps.site_build.outputs.status != '0'
+ if: steps.validate.outputs.status != 'success' || (steps.site_changes.outputs.changed == 'true' && steps.site_build.outputs.status != '0')
run: exit 1