notACMS 1.2.0 — the audit release
A full code, security, and theme audit turned into one release: ~170 fixes, a hardened build pipeline, a documented theme API, and a handful of deliberate breaking changes.
The audit release
1.2.0 is the result of a complete audit of notACMS — architecture, security, code quality, and both template trees. Around 170 findings were fixed. The highlights:
Robustness
- A markdown file with broken frontmatter no longer takes down a locale or the build — it's skipped and reported with its path and error.
- A missing
slug:can no longer silently hijack the homepage;posts_per_page: 0can no longer crash every blog page; duplicate URLs, ambiguous directory keys, and invalid tag values now produce clear build warnings. - Draft and scheduled pages are now genuinely excluded from builds, menus, and
/llms.txt— previously only posts were filtered. app:build -o <dir>refuses to wipe a directory other than the configured static dir unless you pass--force.- Visiting a
/pl/link for the first time no longer bounces you to EN — the URL is treated as your language choice and a cookie is set. The locale cookie is now alsoSecure-conditional so the mechanism works in HTTP dev environments. - Search result excerpts now render highlighted terms as actual
<mark>highlights instead of literal<mark>text. - The bare starter theme's
_site.yamlnow includes placeholdercontact_formvalues — no more build error on a fresh install.
Security
- ImageMagick runs through
Symfony\Processwith argv arrays —exec()is gone. - Turnstile now validates the response hostname against your
base_urland logs an error when the committed always-pass test keys are active in production. A boot check warns whenAPP_SECRETis still the placeholder. - JSON-LD output is hex-escaped (
</script>in a title can't break out), translation catalogs no longer carry HTML, and the nginx security headers now also apply to/assets/and/media/responses. - The locale-prefixed contact API was unreachable in the Docker runtime deployment due to an nginx regex bug — fixed.
For theme builders
- New THEME_BUILDING.md: the full theme API contract — per-route template contexts, Twig functions and globals, the ContentItem API, and the translation keys every theme must define.
#[LocalizedRoute]now works inlocal/src/Controller/, language switching works correctly on 3+-locale sites, and the static build pipeline is decomposed into reusable services.
New
- The static build now produces a
/llms.txtfeed per locale — the most recent posts in a machine-readable format for LLM context windows. Configurable viallms_limitin_site.yaml(default: 5). The template is overridable per-theme via the@baseTwig namespace.
Breaking changes
A few — each with a one-line migration. directoryKey() returns full content paths (basename lookups still work), getTree() moved to ContentTreeProviderInterface, blogPosting() takes a named map, the lang_switch_url context key is gone, four dead translation keys were removed, and the 1.0 old-template package was dropped. See UPGRADE-1.2.md for the complete guide.