mirror of
https://github.com/sbroenne/mcp-server-excel.git
synced 2026-09-19 07:53:08 +08:00
8310b3f27c
* ci(docs): validate the Pages site on PRs and harden the build The docs site was validated by nothing before merge: CI Gate never built it, and pre-commit.ps1 classifies gh-pages/ as doc-only. A broken site was only discoverable after it had already deployed. Validation - New "Docs Site" CI job: mkdocs build --strict, audit_site.py and the new check_deploy_paths.py on every pull request. - check_deploy_paths.py proves the deploy workflow paths: filter covers every source hooks.py mirrors. That filter was hand-maintained, so a new mirrored file silently stopped triggering a deploy. - Dependabot pip entry for gh-pages: mkdocs-material was never updating. Build fixes - hooks.py wrote its generated pages into docs_dir, which mkdocs serve watches, so every build retriggered itself and looped forever. Generated output moved to gh-pages/_generated/ with snippets base_path extended to match. Rebuild detections after one edit: 9+ -> 1. - Added a watch: key so serve reloads on FEATURES.md, CHANGELOG.md, docs/ and skills/ instead of only on gh-pages/. Workflow split - Persisting the star history needs contents: write, and it shared a job with pip install -r requirements.txt. Split into star-history.yml; the Pages build is now contents: read and only renders the chart. The generator tests also run on pull requests now, not just after merge. - New link-check.yml runs lychee weekly over the built site and files an issue on link rot. Scheduled rather than per-PR because external endpoints rate-limit and would make it flaky. Site output is byte-identical across all 164 files. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 388229c1-868a-416f-8b0a-7d6ee058e788 * fix(docs): keep excluding the former docs/_generated/ location Moving generated output out of docs_dir also removed the exclude_docs entry, so a stale docs/_generated/ left in a long-lived clone was silently built and published as real pages: reproduced at 52 pages instead of 51, with audit_site.py passing. Restore the exclusion and keep the old .gitignore entry so the leftover directory neither gets published nor shows up as untracked. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 388229c1-868a-416f-8b0a-7d6ee058e788 --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 388229c1-868a-416f-8b0a-7d6ee058e788
92 lines
3.1 KiB
Python
92 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Verify the Pages deploy workflow rebuilds the site for every canonical source.
|
|
|
|
``hooks.py`` pulls canonical Markdown from all over the repo into the site, but
|
|
``.github/workflows/deploy-gh-pages.yml`` decides *when* to rebuild from a
|
|
hand-written ``paths:`` filter. Nothing kept the two in sync, so adding a new
|
|
mirrored source silently produced a stale website until the next nightly cron.
|
|
|
|
This script fails when a source that ``hooks.py`` reads is not covered by the
|
|
workflow filter. Run it from the ``gh-pages`` directory::
|
|
|
|
python check_deploy_paths.py
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import fnmatch
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
GH_PAGES = Path(__file__).resolve().parent
|
|
REPO_ROOT = GH_PAGES.parent
|
|
WORKFLOW = REPO_ROOT / ".github" / "workflows" / "deploy-gh-pages.yml"
|
|
|
|
# Literal _read("...") calls in hooks.py: CHANGELOG.md, SECURITY.md, the
|
|
# installation pages and so on. The dict-driven sources are collected from the
|
|
# imported module instead, because those paths are built with f-strings.
|
|
_READ_CALL = re.compile(r'_read\(\s*"([^"]+)"')
|
|
|
|
|
|
def mirrored_sources() -> set[str]:
|
|
sys.path.insert(0, str(GH_PAGES))
|
|
import hooks # noqa: PLC0415 - deliberate late import; needs sys.path above
|
|
|
|
sources: set[str] = set()
|
|
sources.update(hooks.FEATURE_SOURCES.values())
|
|
sources.update(hooks.GUIDE_SOURCES.values())
|
|
sources.update(f"skills/shared/{name}" for name in hooks.SKILL_SOURCES)
|
|
sources.update(_READ_CALL.findall((GH_PAGES / "hooks.py").read_text(encoding="utf-8")))
|
|
return sources
|
|
|
|
|
|
def workflow_paths() -> list[str]:
|
|
config = yaml.safe_load(WORKFLOW.read_text(encoding="utf-8"))
|
|
# YAML 1.1 parses a bare `on:` key as the boolean True.
|
|
triggers = config.get("on", config.get(True, {})) or {}
|
|
return list((triggers.get("push") or {}).get("paths") or [])
|
|
|
|
|
|
def main() -> int:
|
|
if not WORKFLOW.is_file():
|
|
print(f"ERROR: {WORKFLOW} not found", file=sys.stderr)
|
|
return 2
|
|
|
|
patterns = workflow_paths()
|
|
if not patterns:
|
|
print("ERROR: the deploy workflow declares no push paths filter", file=sys.stderr)
|
|
return 2
|
|
|
|
missing: list[str] = []
|
|
for source in sorted(mirrored_sources()):
|
|
if not (REPO_ROOT / source).is_file():
|
|
missing.append(f"{source}: read by hooks.py but does not exist in the repo")
|
|
continue
|
|
if not any(fnmatch.fnmatch(source, pattern) for pattern in patterns):
|
|
missing.append(
|
|
f"{source}: mirrored into the site but no paths: entry in "
|
|
f"{WORKFLOW.relative_to(REPO_ROOT).as_posix()} matches it"
|
|
)
|
|
|
|
if missing:
|
|
print(
|
|
"Deploy path check FAILED - editing these files would not rebuild "
|
|
f"the site:\n"
|
|
)
|
|
for item in missing:
|
|
print(f" - {item}")
|
|
return 1
|
|
|
|
print(
|
|
f"Deploy path check passed: {len(mirrored_sources())} mirrored sources, "
|
|
f"all covered by {len(patterns)} paths entries."
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|