|
| 1 | +# Adding an article |
| 2 | + |
| 3 | +Steps to publish a new article and regenerate the public artifacts that depend on it. |
| 4 | + |
| 5 | +## 1. Add the article data |
| 6 | + |
| 7 | +Add a new entry to the category's `articles` array in `src/App/src/Fixture/articles_cleaned.json`: |
| 8 | + |
| 9 | +```json |
| 10 | +{ |
| 11 | + "post_title": "Your article title", |
| 12 | + "post_date": "YYYY-MM-DD HH:MM:SS", |
| 13 | + "post_status": "publish", |
| 14 | + "author": { |
| 15 | + "display_name": "admin", |
| 16 | + "github": "arhimede" |
| 17 | + }, |
| 18 | + "isObsolete": false, |
| 19 | + "opengraph_img": null, |
| 20 | + "excerpt": "Short excerpt shown in listings.", |
| 21 | + "tl_dr": "One or two sentence summary." |
| 22 | +} |
| 23 | +``` |
| 24 | + |
| 25 | +`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()`. |
| 26 | + |
| 27 | +`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`. |
| 28 | + |
| 29 | +**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. |
| 30 | + |
| 31 | +## 2. Create the templates |
| 32 | + |
| 33 | +- `src/Blog/templates/page/blog-resource/{category-slug}/{article-slug}.html.twig` — the page body, extending `@layout/blog-post.html.twig`. |
| 34 | +- `src/Blog/templates/page/JSON-LD/{category-slug}/{article-slug}.jsonld.twig` — the `@graph` of `TechArticle` + `BreadcrumbList` + `FAQPage` structured data. |
| 35 | +- `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`. |
| 36 | + |
| 37 | +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.). |
| 38 | + |
| 39 | +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. |
| 40 | + |
| 41 | +## 3. At deploy — run in this order |
| 42 | + |
| 43 | +``` |
| 44 | +php bin/doctrine-fixtures |
| 45 | +php bin/create-uploads-dir |
| 46 | +``` |
| 47 | + |
| 48 | +- `bin/doctrine-fixtures` loads `articles_cleaned.json` into the database, creating the `Post` entity (with its database-generated UUID) for the new article. |
| 49 | +- `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`. |
| 50 | + |
| 51 | +## 4. Regenerate the public artifacts — any order |
| 52 | + |
| 53 | +``` |
| 54 | +php bin/generate-feed |
| 55 | +php bin/sitemap |
| 56 | +php bin/generate-llms-full |
| 57 | +``` |
| 58 | + |
| 59 | +- `bin/generate-feed` rewrites `public/feed.xml` from the published posts in the database. |
| 60 | +- `bin/sitemap` rewrites `public/sitemap.xml` from the published posts in the database. |
| 61 | +- `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`). |
| 62 | + |
| 63 | +These three have no ordering dependency on each other, only on step 3 being done first. |
| 64 | + |
| 65 | +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. |
0 commit comments