6464 python-version : " 3.12"
6565 cache : pip
6666
67+ - name : Detect TechAPI homepage changes
68+ id : site_changes
69+ shell : bash
70+ run : |
71+ set -euo pipefail
72+ python - <<'PY' >> "$GITHUB_OUTPUT"
73+ from __future__ import annotations
74+
75+ import hashlib
76+ import re
77+ from pathlib import Path
78+
79+ def digest(path: Path) -> str:
80+ return hashlib.sha256(path.read_bytes()).hexdigest()
81+
82+ def rel_site_files(root: Path) -> dict[str, Path]:
83+ site = root / "site"
84+ if not site.exists():
85+ return {}
86+ wanted_files = {"site/package.json", "site/package-lock.json"}
87+ files: dict[str, Path] = {}
88+ for path in sorted(site.rglob("*")):
89+ if not path.is_file():
90+ continue
91+ rel = str(path.relative_to(root)).replace("\\", "/")
92+ if (
93+ rel.startswith("site/src/")
94+ or rel.startswith("site/public/")
95+ or rel in wanted_files
96+ or re.match(r"site/astro\.config\.[cm]?[jt]s$", rel)
97+ ):
98+ if rel.startswith("site/public/v1/") or rel == "site/public/openapi.json":
99+ continue
100+ files[rel] = path
101+ return files
102+
103+ head = rel_site_files(Path("TechAPI"))
104+ base = rel_site_files(Path("TechAPI-main"))
105+ added = sorted(set(head) - set(base))
106+ deleted = sorted(set(base) - set(head))
107+ modified = sorted(key for key in set(head) & set(base) if digest(head[key]) != digest(base[key]))
108+ has_changes = bool(added or modified or deleted)
109+ print(f"changed={'true' if has_changes else 'false'}")
110+ print(f"added={len(added)}")
111+ print(f"modified={len(modified)}")
112+ print(f"deleted={len(deleted)}")
113+ PY
114+
67115 - uses : actions/setup-node@v4
116+ if : steps.site_changes.outputs.changed == 'true'
68117 with :
69118 node-version : " 22"
70119 cache : npm
@@ -105,6 +154,7 @@ jobs:
105154 echo "integrity_status=${integrity_status:-1}" >> "$GITHUB_OUTPUT"
106155
107156 - name : Build TechAPI homepage
157+ if : steps.site_changes.outputs.changed == 'true'
108158 id : site_build
109159 shell : bash
110160 run : |
@@ -419,11 +469,13 @@ jobs:
419469 run : |
420470 short_sha="${TECHAPI_HEAD_SHA:0:7}"
421471 result="PASS"
422- if [ "${{ steps.validate.outputs.status }}" != "success" ] || [ "${{ steps.site_build.outputs.status }}" != "0" ]; then
472+ site_changed="${{ steps.site_changes.outputs.changed }}"
473+ site_build_status="${{ steps.site_build.outputs.status }}"
474+ if [ "${{ steps.validate.outputs.status }}" != "success" ] || { [ "${site_changed}" = "true" ] && [ "${site_build_status}" != "0" ]; }; then
423475 result="FAIL"
424476 fi
425477
426- VALIDATION_STATUS="${{ steps.validate.outputs.status }}" SITE_BUILD_STATUS="${{ steps.site_build.outputs.status }}" python - <<'PY'
478+ VALIDATION_STATUS="${{ steps.validate.outputs.status }}" SITE_CHANGED="${{ steps.site_changes.outputs.changed }}" SITE_BUILD_STATUS="${{ steps.site_build.outputs.status }}" python - <<'PY'
427479 from __future__ import annotations
428480
429481 import os
@@ -434,7 +486,8 @@ jobs:
434486 log = log_path.read_text(encoding="utf-8", errors="replace")
435487 lines = log.splitlines()
436488 failed = os.environ.get("VALIDATION_STATUS") != "success"
437- site_failed = os.environ.get("SITE_BUILD_STATUS") != "0"
489+ site_changed = os.environ.get("SITE_CHANGED") == "true"
490+ site_failed = site_changed and os.environ.get("SITE_BUILD_STATUS") != "0"
438491
439492 section_counts: dict[str, int] = {}
440493 current_section: str | None = None
@@ -491,28 +544,29 @@ jobs:
491544 out.append("")
492545 out.append("</details>")
493546
494- site_log_path = Path("site-build.log")
495- site_log = site_log_path.read_text(encoding="utf-8", errors="replace") if site_log_path.exists() else ""
496- site_log = re.sub(r"\x1b\[[0-9;]*m", "", site_log)
497- if site_failed:
498- out.append("")
499- out.append("<details><summary>Detailed homepage build log excerpt</summary>")
500- out.append("")
501- out.append("```text")
502- excerpt = site_log[-12000:] if len(site_log) > 12000 else site_log
503- out.append(excerpt.rstrip())
504- out.append("```")
505- out.append("")
506- out.append("</details>")
507- elif site_log:
508- summary = [line for line in site_log.splitlines() if "Complete!" in line or "page(s) built" in line]
509- if summary:
547+ if site_changed:
548+ site_log_path = Path("site-build.log")
549+ site_log = site_log_path.read_text(encoding="utf-8", errors="replace") if site_log_path.exists() else ""
550+ site_log = re.sub(r"\x1b\[[0-9;]*m", "", site_log)
551+ if site_failed:
510552 out.append("")
511- out.append("Homepage build: ")
553+ out.append("<details><summary>Detailed homepage build log excerpt</summary> ")
512554 out.append("")
513555 out.append("```text")
514- out.extend(summary[-4:])
556+ excerpt = site_log[-12000:] if len(site_log) > 12000 else site_log
557+ out.append(excerpt.rstrip())
515558 out.append("```")
559+ out.append("")
560+ out.append("</details>")
561+ elif site_log:
562+ summary = [line for line in site_log.splitlines() if "Complete!" in line or "page(s) built" in line]
563+ if summary:
564+ out.append("")
565+ out.append("Homepage build:")
566+ out.append("")
567+ out.append("```text")
568+ out.extend(summary[-4:])
569+ out.append("```")
516570
517571 Path("validation-notes.md").write_text("\n".join(out) + "\n", encoding="utf-8")
518572 PY
@@ -531,11 +585,15 @@ jobs:
531585 echo "| --- | --- |"
532586 echo "| \`python -m app.validate\` | $([ "${{ steps.validate.outputs.app_status }}" = "0" ] && echo PASS || echo FAIL) |"
533587 echo "| \`python integrity_check.py TechAPI/data --strict\` | $([ "${{ steps.validate.outputs.integrity_status }}" = "0" ] && echo PASS || echo FAIL) |"
534- echo "| \`cd TechAPI/site && npm ci && npm run build\` | $([ "${{ steps.site_build.outputs.status }}" = "0" ] && echo PASS || echo FAIL) |"
588+ if [ "${site_changed}" = "true" ]; then
589+ echo "| \`cd TechAPI/site && npm ci && npm run build\` | $([ "${site_build_status}" = "0" ] && echo PASS || echo FAIL) |"
590+ fi
535591 echo
536592 cat change-review.md
537- echo
538- cat site-change-review.md
593+ if [ "${site_changed}" = "true" ]; then
594+ echo
595+ cat site-change-review.md
596+ fi
539597 } > change-comment.md
540598
541599 {
@@ -586,5 +644,5 @@ jobs:
586644 run : echo "::warning::TECHENGINEBOT_TOKEN/TECHAPI_TOKEN is not configured; validation ran but no PR comment was posted."
587645
588646 - name : Fail on validation errors
589- if : steps.validate.outputs.status != 'success' || steps.site_build.outputs.status != '0'
647+ if : steps.validate.outputs.status != 'success' || ( steps.site_changes.outputs.changed == 'true' && steps. site_build.outputs.status != '0')
590648 run : exit 1
0 commit comments