msos/tools
popovskik 596290acf8 feat(cms): U2 render content/ into detail pages (characterization-verified)
Add tools/build_content.py: renders every content/ entry into its blog /
news / event detail page by self-templating the page's own chrome and
replacing only content-derived regions (title, article header, hero,
body, and — for new pages only — the scrollspy).

Faithful reproduction, proven against the live site:
- body_format: html fragments injected verbatim, so guide-boxes, inline
  figures and event galleries render unchanged.
- scrollspy sidebars are preserved (labels are hand-curated, shorter than
  the H2s); only generated when a page has none.
- <title> suffix derived per page, so EN/SI " - MSOS", MK " - МСОС", and
  suffix-less event titles all reproduce exactly.
- hero /images/… rewritten to the page's ../../../ depth.
- ASCII-only console output (no cp1252 crash on Windows/CI).

tools/tests/test_build_content.py: 8 characterization tests; the core one
renders all 51 pages and asserts each is semantically equivalent
(whitespace/attr-order-insensitive) to the current page — the R5
no-regression guarantee.

Does NOT overwrite the committed pages; whether generated pages are
committed vs built fresh in CI is decided in U4/U5. Run:
  python tools/build_content.py --check   # verify equivalence
  python tools/build_content.py --write    # emit pages (for U4)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-08 16:36:11 +02:00
..
__pycache__ GEO: FAQPage/NGO/Event schema + autonomous generation for new projects 2026-08-07 22:17:03 +02:00
git-hooks GEO: FAQPage/NGO/Event schema + autonomous generation for new projects 2026-08-07 22:17:03 +02:00
tests feat(cms): U2 render content/ into detail pages (characterization-verified) 2026-08-08 16:36:11 +02:00
README.md Add technical SEO foundation, MK/SI about-us + timeline, content fixes 2026-08-02 11:43:10 +02:00
build_blog.py Blog: add personal essay "How I coped with leaving home" (trilingual, prose) 2026-08-05 17:37:14 +02:00
build_botlinks.py Bot pages: use root-relative links so they work on any host 2026-08-05 18:18:32 +02:00
build_content.py feat(cms): U2 render content/ into detail pages (characterization-verified) 2026-08-08 16:36:11 +02:00
build_listings.py GEO: FAQPage/NGO/Event schema + autonomous generation for new projects 2026-08-07 22:17:03 +02:00
build_search_index.py feat(assistant): v2 — query-language detection, analytics, chips, auto-index 2026-08-04 01:48:39 +02:00
migrate_pages_to_content.py feat(cms): U1 content model + migration scaffold for blog/news/events 2026-08-08 16:18:29 +02:00
seo_inject.py GEO: FAQPage/NGO/Event schema + autonomous generation for new projects 2026-08-07 22:17:03 +02:00

README.md

tools/

seo_inject.py

Injects a technical-SEO block into every language page (en|mk|si/**/index.html) and regenerates sitemap.xml + robots.txt. Safe to re-run — the block lives between <!-- SEO:START --> and <!-- SEO:END --> and is replaced in place.

The block adds: meta description (auto-extracted from the page's lead/subtitle where available), canonical, hreflang alternates (en / mk / sl / x-default), Open Graph, Twitter Card, and an Organization JSON-LD.

All absolute URLs point at the final domain https://msosorg.com (not the temporary host), so nothing needs rewriting at launch. Note: the Slovene folder is /si/ but the ISO code is sl — the script maps si → sl for hreflang/locale.

Usage

python tools/seo_inject.py            # dry run: print the block for pilot pages
python tools/seo_inject.py --all      # write the block into every page
python tools/seo_inject.py --all --sitemap   # also rebuild sitemap.xml + robots.txt

Run --all --sitemap after adding, renaming, or deleting any page so canonical tags, hreflang alternates, and the sitemap stay in sync.