Steps to publish a new article and regenerate the public artifacts that depend on it.
Add a new entry to the category's articles array in src/App/src/Fixture/articles_cleaned.json:
{
"post_title": "Your article title",
"post_date": "YYYY-MM-DD HH:MM:SS",
"post_status": "publish",
"author": {
"display_name": "admin",
"github": "arhimede"
},
"isObsolete": false,
"opengraph_img": null,
"excerpt": "Short excerpt shown in listings.",
"tl_dr": "One or two sentence summary."
}author.display_name can either match an existing author or be a new name — bin/doctrine-fixtures creates a new Author automatically for any name not already in the database. The category (top-level slug) must already exist, though. The article's slug is derived automatically from the title (lowercased, non-alphanumeric characters collapsed to -) by PostLoader::slugify().
opengraph_img is the image shown as the social-media (Twitter/OG) preview card. Leave it null to fall back to the site-wide default image (config/autoload/local.php → application.meta.image). To set one, put the image file at public/opengraph/article/your-image.png and reference it here as a root-relative path: "opengraph_img": "/opengraph/article/your-image.png". This is unrelated to the in-article images described in step 3 — it is placed by hand, not by bin/create-uploads-dir.
Important: you can set "post_status": "draft" instead of "publish" to keep an article out of sight — anything other than publish/private is treated as a draft by PostLoader, and getPublishedPosts() (used by both bin/generate-feed and bin/sitemap) only returns posts with publish status. After changing it, follow the same steps: re-run bin/doctrine-fixtures, then bin/generate-feed and bin/sitemap. This applies generally, not just to status changes — any edit to articles_cleaned.json (title, excerpt, status, date, etc.) needs bin/doctrine-fixtures re-run to update the database, followed by re-running the 3 generators in step 4 so feed.xml/sitemap.xml/llms-full.txt reflect it. One exception: bin/generate-llms-full reads straight from the .md files on disk and does not check post_status at all — a draft article's .md file will still be included in llms-full.txt unless you also remove or rename that file.
src/Blog/templates/page/blog-resource/{category-slug}/{article-slug}.html.twig— the page body, extending@layout/blog-post.html.twig.src/Blog/templates/page/JSON-LD/{category-slug}/{article-slug}.jsonld.twig— the@graphofTechArticle+BreadcrumbList+FAQPagestructured data.public/md-articles/{category-slug}/{article-slug}.md— the markdown version, with YAML front matter (title,description,author,date_published,canonical_url,category,language) followed by the article body (TL;DR, sections,FAQ). This feedsllms-full.txt.
Copy an existing set of these three files in the same category as a starting point, to match the established structure (FAQ block matching the FAQPage entries, etc.).
If the article body uses images (via asset('uploads/article/' ~ article.id ~ '/filename.png') in the .html.twig), just drop the image file anywhere under public/uploads — bin/create-uploads-dir (step 4) finds it by filename and copies it to the right place. No manual path/folder creation needed.
php bin/doctrine-fixtures
php bin/create-uploads-dirbin/doctrine-fixturesloadsarticles_cleaned.jsoninto the database, creating thePostentity (with its database-generated UUID) for the new article.bin/create-uploads-dirmust run after it — it resolves the post by slug to get that UUID, createspublic/uploads/article/{post-id}/, and copies each image referenced in the.html.twigthere from wherever it already lives underpublic/uploads.
php bin/generate-feed
php bin/sitemap
php bin/generate-llms-fullbin/generate-feedrewritespublic/feed.xmlfrom the published posts in the database.bin/sitemaprewritespublic/sitemap.xmlfrom the published posts in the database.bin/generate-llms-fullrewritespublic/llms-full.txtby concatenatingpublic/md-articles/index.mdand every otherpublic/md-articles/*/*.mdfile, sorted by path, then appending eachpublic/md-pages/*.mdfile — the markdown versions of the static pages — labelled with amd-pages/prefix in the section header. Requires thellms.sourceDir/llms.outputFilekeys inconfig/autoload/local.php(seelocal.php.dist); thellms.pagesDirkey is optional, and omitting it leaves the page sections out.
These three have no ordering dependency on each other, only on step 3 being done first.
None of this is wired into an automated deploy pipeline in this repository — there is no deploy script or CI job that runs these bin/ scripts. public/feed.xml, public/sitemap.xml, and public/llms-full.txt are committed generated artifacts, so re-running these scripts leaves them modified in git until committed.
bin/generate-packages— the only script here actually wired into a cron job. It rebuilds the Dotkernel packages listing from the GitHub organisation, which changes independently of this repo, so it runs on a schedule instead of at deploy time:0 4 * * * cd /path/to/dotkernel.com && /usr/bin/php bin/generate-packages >> log/generate-packages.log 2>&1- Runs daily at 04:00.
- Exits non-zero without touching the data file if the run can't be trusted, so the previously generated listing keeps serving.
- Logs to
log/generate-packages.log.
bin/generate-feed(RSS,public/feed.xml) — manual only, no cron. Run it as part of the publish flow in step 4, right afterbin/doctrine-fixtures/bin/create-uploads-dir, whenever an article is added, edited, or itspost_statuschanges.bin/sitemap(public/sitemap.xml) — manual only, no cron. Same trigger asbin/generate-feed: re-run after any change toarticles_cleaned.json.bin/generate-llms-full(public/llms-full.txt) — manual only, no cron. Re-run after adding/editing a.mdfile underpublic/md-articles/orpublic/md-pages/, or after removing/renaming a draft's.mdfile (it doesn't checkpost_status, see step 1). Thepublic/md-pages/*.mdfiles are hand-maintained alongside the templates insrc/Page/templates/page/— editing a page template means updating its.mdand re-running this.