From 581de22db2fdada84bf6d455e41af472c1100833 Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Thu, 6 Aug 2026 17:01:26 +0300 Subject: [PATCH 1/5] readme.md complete with instructions --- README.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/README.md b/README.md index e69de29..7d69913 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,65 @@ +# Adding an article + +Steps to publish a new article and regenerate the public artifacts that depend on it. + +## 1. Add the article data + +Add a new entry to the category's `articles` array in `src/App/src/Fixture/articles_cleaned.json`: + +```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. + +## 2. Create the templates + +- `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 `@graph` of `TechArticle` + `BreadcrumbList` + `FAQPage` structured 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 feeds `llms-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. + +## 3. At deploy — run in this order + +``` +php bin/doctrine-fixtures +php bin/create-uploads-dir +``` + +- `bin/doctrine-fixtures` loads `articles_cleaned.json` into the database, creating the `Post` entity (with its database-generated UUID) for the new article. +- `bin/create-uploads-dir` must run *after* it — it resolves the post by slug to get that UUID, creates `public/uploads/article/{post-id}/`, and copies each image referenced in the `.html.twig` there from wherever it already lives under `public/uploads`. + +## 4. Regenerate the public artifacts — any order + +``` +php bin/generate-feed +php bin/sitemap +php bin/generate-llms-full +``` + +- `bin/generate-feed` rewrites `public/feed.xml` from the published posts in the database. +- `bin/sitemap` rewrites `public/sitemap.xml` from the published posts in the database. +- `bin/generate-llms-full` rewrites `public/llms-full.txt` by concatenating `public/md-articles/index.md` and every other `public/md-articles/*/*.md` file, sorted by path. Requires the `llms.sourceDir` / `llms.outputFile` keys in `config/autoload/local.php` (see `local.php.dist`). + +These three have no ordering dependency on each other, only on step 3 being done first. + +Note: 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. They're run manually (or via cron, as already set up for `bin/generate-packages`). `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. From 22709c488dbc069b156b100d859e21f774ffc5f1 Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Thu, 6 Aug 2026 17:04:30 +0300 Subject: [PATCH 2/5] readme.md complete with instructions --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7d69913..11be59e 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ If the article body uses images (via `asset('uploads/article/' ~ article.id ~ '/ ## 3. At deploy — run in this order -``` +```shell php bin/doctrine-fixtures php bin/create-uploads-dir ``` @@ -50,7 +50,7 @@ php bin/create-uploads-dir ## 4. Regenerate the public artifacts — any order -``` +```shell php bin/generate-feed php bin/sitemap php bin/generate-llms-full From 430dd9e20457a867d15a0cc9919f9b07cfc5d2d2 Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Fri, 7 Aug 2026 13:54:25 +0300 Subject: [PATCH 3/5] Updated readme with cron job instructions --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 11be59e..bdcc31f 100644 --- a/README.md +++ b/README.md @@ -62,4 +62,17 @@ php bin/generate-llms-full These three have no ordering dependency on each other, only on step 3 being done first. -Note: 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. They're run manually (or via cron, as already set up for `bin/generate-packages`). `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. +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. + +## 5. Scheduled jobs (cron) + +- **`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 after `bin/doctrine-fixtures`/`bin/create-uploads-dir`, whenever an article is added, edited, or its `post_status` changes. +- **`bin/sitemap`** (`public/sitemap.xml`) — **manual only**, no cron. Same trigger as `bin/generate-feed`: re-run after any change to `articles_cleaned.json`. +- **`bin/generate-llms-full`** (`public/llms-full.txt`) — **manual only**, no cron. Re-run after adding/editing a `.md` file under `public/md-articles/`, or after removing/renaming a draft's `.md` file (it doesn't check `post_status`, see step 1). From 61114928ee7ba1163f381477cd04847da093e136 Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Fri, 7 Aug 2026 13:57:57 +0300 Subject: [PATCH 4/5] Updated readme with cron job instructions --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bdcc31f..6a336b4 100644 --- a/README.md +++ b/README.md @@ -67,12 +67,12 @@ None of this is wired into an automated deploy pipeline in this repository — t ## 5. Scheduled jobs (cron) - **`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`. + ``` + 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 after `bin/doctrine-fixtures`/`bin/create-uploads-dir`, whenever an article is added, edited, or its `post_status` changes. - **`bin/sitemap`** (`public/sitemap.xml`) — **manual only**, no cron. Same trigger as `bin/generate-feed`: re-run after any change to `articles_cleaned.json`. - **`bin/generate-llms-full`** (`public/llms-full.txt`) — **manual only**, no cron. Re-run after adding/editing a `.md` file under `public/md-articles/`, or after removing/renaming a draft's `.md` file (it doesn't check `post_status`, see step 1). From 6172ee2dc303a1e0e4c0225837ce1c3eac7f29f7 Mon Sep 17 00:00:00 2001 From: OStefan2001 Date: Fri, 7 Aug 2026 14:00:42 +0300 Subject: [PATCH 5/5] Updated readme with cron job instructions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6a336b4..7eaf5eb 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ None of this is wired into an automated deploy pipeline in this repository — t ## 5. Scheduled jobs (cron) - **`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: - ``` + ```text 0 4 * * * cd /path/to/dotkernel.com && /usr/bin/php bin/generate-packages >> log/generate-packages.log 2>&1 ``` - Runs daily at **04:00**.