1616use function file_put_contents ;
1717use function glob ;
1818use function implode ;
19+ use function in_array ;
1920use function preg_match ;
2021use function sprintf ;
2122use function strcasecmp ;
@@ -35,6 +36,8 @@ class LlmsGenerator
3536 'middleware ' ,
3637 'architecture ' ,
3738 'design-pattern ' ,
39+ 'how-to ' ,
40+ 'best-practice ' ,
3841 ];
3942
4043 /**
@@ -43,8 +46,6 @@ class LlmsGenerator
4346 * @var list<non-empty-string>
4447 */
4548 private const OPTIONAL_CATEGORIES = [
46- 'best-practice ' ,
47- 'how-to ' ,
4849 'javascript ' ,
4950 'php-troubleshooting ' ,
5051 'licensing ' ,
@@ -70,6 +71,24 @@ class LlmsGenerator
7071 'design-pattern ' => 'naming conventions for PSR-15 handlers ' ,
7172 ];
7273
74+ /**
75+ * Static page slugs listed under `## Docs` instead of `## Pages` - reference/setup content
76+ * rather than product pages.
77+ *
78+ * @var list<non-empty-string>
79+ */
80+ private const array DOCS_PAGES = [
81+ 'architecture ' ,
82+ 'wsl2 ' ,
83+ 'dotboost ' ,
84+ ];
85+
86+ /**
87+ * Slug of the packages lifecycle page, hardcoded under `## Docs` in `buildHeader()` -
88+ * excluded from `## Pages` here so it is not listed twice.
89+ */
90+ private const string OSS_LIFECYCLE_SLUG = 'dotkernel-packages-oss-lifecycle ' ;
91+
7392 public function __construct (
7493 private readonly PostRepository $ postRepository ,
7594 private readonly string $ sourceDir ,
@@ -106,7 +125,7 @@ public function write(): int
106125 }
107126 $ optionalCategoryCount ++;
108127 foreach ($ postsByCategory [$ slug ] as $ entry ) {
109- $ optionalLines [] = $ this ->buildPostLink ($ slug , $ entry );
128+ $ optionalLines [] = $ this ->buildOptionalPostLink ($ slug , $ entry );
110129 }
111130 unset($ postsByCategory [$ slug ]);
112131 }
@@ -197,6 +216,22 @@ private function buildPostLink(string $slug, array $entry): string
197216 );
198217 }
199218
219+ /**
220+ * @param array{post: Post, title: string, description: string} $entry
221+ */
222+ private function buildOptionalPostLink (string $ slug , array $ entry ): string
223+ {
224+ return sprintf (
225+ '- [%s](%s/%s/%s.md) *(%s)*: %s ' ,
226+ $ entry ['title ' ],
227+ $ this ->baseUrl ,
228+ $ slug ,
229+ $ entry ['post ' ]->getSlug (),
230+ $ entry ['post ' ]->getCategory ()->getName (),
231+ $ entry ['description ' ],
232+ );
233+ }
234+
200235 /**
201236 * @return array{title: string, description: string}
202237 */
@@ -223,6 +258,11 @@ private function buildPagesSection(): string
223258
224259 $ pages = [];
225260 foreach (glob ($ this ->pagesDir . '/*.md ' ) ?: [] as $ path ) {
261+ $ slug = basename ($ path , '.md ' );
262+ if ($ slug === self ::OSS_LIFECYCLE_SLUG || in_array ($ slug , self ::DOCS_PAGES , true )) {
263+ continue ;
264+ }
265+
226266 $ contents = file_get_contents ($ path );
227267 if ($ contents === false ) {
228268 continue ;
@@ -236,7 +276,7 @@ private function buildPagesSection(): string
236276
237277 $ pages [] = [
238278 'title ' => trim (explode ('| ' , $ title , 2 )[0 ]),
239- 'slug ' => basename ( $ path , ' .md ' ) ,
279+ 'slug ' => $ slug ,
240280 'description ' => $ description ,
241281 ];
242282 }
@@ -261,6 +301,41 @@ private function buildPagesSection(): string
261301 return implode ("\n" , $ lines ) . "\n\n" ;
262302 }
263303
304+ /**
305+ * Adds `self::DOCS_PAGES` under `## Docs`
306+ */
307+ private function buildDocsPageLinks (): string
308+ {
309+ if ($ this ->pagesDir === null ) {
310+ return '' ;
311+ }
312+
313+ $ lines = [];
314+ foreach (self ::DOCS_PAGES as $ slug ) {
315+ $ path = sprintf ('%s/%s.md ' , $ this ->pagesDir , $ slug );
316+ $ contents = @file_get_contents ($ path );
317+ if ($ contents === false ) {
318+ continue ;
319+ }
320+
321+ $ title = $ this ->extractFrontMatterField ($ contents , 'title ' );
322+ $ description = $ this ->extractFrontMatterField ($ contents , 'description ' );
323+ if ($ title === null || $ description === null ) {
324+ continue ;
325+ }
326+
327+ $ lines [] = sprintf (
328+ '- [%s](%s/%s.md): %s ' ,
329+ trim (explode ('| ' , $ title , 2 )[0 ]),
330+ $ this ->baseUrl ,
331+ $ slug ,
332+ $ description ,
333+ );
334+ }
335+
336+ return $ lines === [] ? '' : implode ("\n" , $ lines ) . "\n" ;
337+ }
338+
264339 private function extractFrontMatterField (string $ contents , string $ field ): ?string
265340 {
266341 if (preg_match (sprintf ('/^%s:\s*"(.*)"\s*$/m ' , $ field ), $ contents , $ matches ) === 1 ) {
@@ -272,15 +347,16 @@ private function extractFrontMatterField(string $contents, string $field): ?stri
272347
273348 private function buildHeader (): string
274349 {
275- $ intro = '> Dotkernel is the technical blog for the Dotkernel headless PHP platform - a PSR-15 '
350+ $ intro = '> dotkernel.com is the technical blog for the Dotkernel headless PHP platform - a PSR-15 '
276351 . 'compliant application built on Mezzio and Laminas components. It publishes architecture '
277352 . 'write-ups, how-tos, and release notes for the platform applications - Dotkernel API, Admin '
278353 . 'and Queue - and for the standalone Dotkernel Light skeleton. ' ;
279354
280355 $ body = 'Content spans foundational PHP/middleware architecture (PSR-7, PSR-15, request lifecycle, '
281356 . 'dependency injection), practical how-tos (Doctrine migrations, CORS, authentication, caching), '
282357 . 'and the history/release notes of the Dotkernel ecosystem going back to 2008. Posts are organized '
283- . 'by category and attributed to an author; URLs follow the pattern `/{category-slug}/{post-slug}/`. ' ;
358+ . 'by category and attributed to an author; URLs follow the pattern `/{category-slug}/{post-slug}/`. '
359+ . 'Each post also has a Markdown version at `/{category-slug}/{post-slug}.md`. ' ;
284360
285361 $ about = 'the team behind Dotkernel - how the team works, its commitment to open source and the PHP '
286362 . 'community, and how it uses AI under guardrails ' ;
@@ -289,10 +365,8 @@ private function buildHeader(): string
289365 . $ intro . "\n\n"
290366 . $ body . "\n\n"
291367 . "## Docs \n\n"
292- . "- [Blog]( {$ this ->baseUrl }/blog/): full list of posts, most recent first, paginated \n"
293- . "- [Categories]( {$ this ->baseUrl }/categories/): all categories with post counts \n"
294- . "- [OSS Package Lifecycle]( {$ this ->baseUrl }/dotkernel-packages-oss-lifecycle/): "
368+ . "- [OSS Package Lifecycle]( {$ this ->baseUrl }/ " . self ::OSS_LIFECYCLE_SLUG . ".md): "
295369 . "support/maintenance status of Dotkernel's open-source packages \n"
296- . " - [Contact]( { $ this ->baseUrl } /contact/) \n \n" ;
370+ . $ this ->buildDocsPageLinks () . " \n" ;
297371 }
298372}
0 commit comments