|
| 1 | +"""Build the redirect-only site this repository now publishes. |
| 2 | +
|
| 3 | +The site moved to https://commit-check.com. This repository is kept as the |
| 4 | +historical source — ``docs/`` is still here and still readable — but what it |
| 5 | +publishes is a set of redirect stubs, one per URL the old site served, so links |
| 6 | +already out in the world land on the page that replaced them. |
| 7 | +
|
| 8 | +Why stubs rather than a server redirect: GitHub Pages serves static files and |
| 9 | +has no redirect table, so a ``<meta refresh>`` plus a ``rel=canonical`` is the |
| 10 | +only mechanism available. The canonical link is what transfers search ranking |
| 11 | +to the new URL; the meta refresh and the script are what move a reader. |
| 12 | +
|
| 13 | +Deliberately no ``robots: noindex`` on these stubs. It reads like the tidy thing |
| 14 | +to do, but it contradicts the canonical: one says "consolidate this page onto |
| 15 | +that URL", the other says "drop this page from the index", and a crawler that |
| 16 | +honours the second may never act on the first — or carry the noindex across to |
| 17 | +the target. A migration wants the canonical to be believed. |
| 18 | +
|
| 19 | +Why this runs instead of ``mkdocs build``: once this repository is archived, |
| 20 | +Actions stop running and the last deployed artifact is what Pages serves |
| 21 | +forever. That artifact needs to be the redirects, so the redirects have to be |
| 22 | +deployed *before* the archive switch is flipped, not after. |
| 23 | +
|
| 24 | +Run with ``python scripts/build_redirects.py`` — output goes to ``site/``. |
| 25 | +""" |
| 26 | + |
| 27 | +from __future__ import annotations |
| 28 | + |
| 29 | +import sys |
| 30 | +from pathlib import Path |
| 31 | + |
| 32 | +NEW_SITE = "https://commit-check.com" |
| 33 | + |
| 34 | +#: Every URL the mkdocs site served, taken from its own build output, mapped to |
| 35 | +#: the path that replaced it on the new site. |
| 36 | +# |
| 37 | +# All but one are the same path: the pages, the blog, its archive, author and |
| 38 | +# category indexes were carried over unchanged, and the posts kept their |
| 39 | +# filenames and ``created`` dates, so the generated slugs match byte for byte. |
| 40 | +# |
| 41 | +# ``/projects/`` is the exception. It was folded into the Ecosystem section of |
| 42 | +# the new landing page, so it redirects to the root rather than to a page that |
| 43 | +# does not exist. |
| 44 | +SAME_PATH = [ |
| 45 | + "/", |
| 46 | + "/getting-started/", |
| 47 | + "/blog/", |
| 48 | + "/blog/2026/06/21/ai-native-json-output-and-a-python-api/", |
| 49 | + "/blog/2026/06/21/from-zero-config-to-org-wide-policy/", |
| 50 | + "/blog/2026/06/21/one-policy-file-for-your-git-history/", |
| 51 | + "/blog/2026/07/06/ai-attribution-governance-enforcing-ai-disclosure-policies-at-the-ci-level/", |
| 52 | + "/blog/archive/2026/", |
| 53 | + "/blog/author/team/", |
| 54 | + "/blog/category/announcements/", |
| 55 | + "/blog/category/updates/", |
| 56 | +] |
| 57 | + |
| 58 | +REDIRECTS = {path: path for path in SAME_PATH} | {"/projects/": "/"} |
| 59 | + |
| 60 | +# The fragment is carried across by the script: a reader following a deep link |
| 61 | +# into a page should keep their place. ``location.replace`` rather than |
| 62 | +# ``location.href`` so the stub does not land in the back-button history and |
| 63 | +# trap them in a loop between the two sites. |
| 64 | +TEMPLATE = """<!doctype html> |
| 65 | +<html lang="en"> |
| 66 | +<head> |
| 67 | +<meta charset="utf-8"> |
| 68 | +<meta name="viewport" content="width=device-width, initial-scale=1"> |
| 69 | +<title>Moved to commit-check.com</title> |
| 70 | +<link rel="canonical" href="{target}"> |
| 71 | +<meta http-equiv="refresh" content="0; url={target}"> |
| 72 | +<script>location.replace("{target}" + location.hash);</script> |
| 73 | +<style> |
| 74 | + body {{ font-family: system-ui, sans-serif; margin: 4rem auto; max-width: 34rem; |
| 75 | + padding: 0 1rem; line-height: 1.6; }} |
| 76 | + a {{ color: #2c9ccd; }} |
| 77 | +</style> |
| 78 | +</head> |
| 79 | +<body> |
| 80 | +<h1>This site has moved</h1> |
| 81 | +<p>The Commit Check documentation, landing page and blog are now published at |
| 82 | +<a href="{target}">{target}</a>.</p> |
| 83 | +<p>If you are not redirected automatically, follow the link above.</p> |
| 84 | +</body> |
| 85 | +</html> |
| 86 | +""" |
| 87 | + |
| 88 | + |
| 89 | +def main() -> int: |
| 90 | + site = Path(__file__).resolve().parent.parent / "site" |
| 91 | + for old, new in REDIRECTS.items(): |
| 92 | + target = NEW_SITE + new |
| 93 | + page = site / old.strip("/") / "index.html" |
| 94 | + page.parent.mkdir(parents=True, exist_ok=True) |
| 95 | + page.write_text(TEMPLATE.format(target=target), encoding="utf-8") |
| 96 | + |
| 97 | + # Pages serves this for any path with no file of its own, which covers the |
| 98 | + # URLs this list missed — a stray deep link, a page from an older layout. |
| 99 | + # It points at the new site's root because there is nothing better to guess. |
| 100 | + (site / "404.html").write_text( |
| 101 | + TEMPLATE.format(target=NEW_SITE + "/"), encoding="utf-8" |
| 102 | + ) |
| 103 | + |
| 104 | + # Without this, Pages runs the output through Jekyll, which skips files and |
| 105 | + # directories whose names begin with an underscore. |
| 106 | + (site / ".nojekyll").write_text("", encoding="utf-8") |
| 107 | + |
| 108 | + print(f"wrote {len(REDIRECTS)} redirects + 404 fallback to {site}") |
| 109 | + return 0 |
| 110 | + |
| 111 | + |
| 112 | +if __name__ == "__main__": |
| 113 | + sys.exit(main()) |
0 commit comments