Skip to content

Commit 3b5388d

Browse files
authored
Merge pull request #190 from dotkernel/missing-md
updated llms generation
2 parents 76eb3a9 + 374c686 commit 3b5388d

13 files changed

Lines changed: 415 additions & 36 deletions

File tree

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ package-lock.json
5454
# Generated by bin/generate-packages (daily cron); not tracked so it never conflicts on pull
5555
/public/dotkernel-packages.json
5656
/public/dotkernel-packages.json.tmp
57+
/public/dotkernel-packages-oss-lifecycle.md
5758

5859
/public/llms-full.txt
5960
/public/llms.txt

‎bin/generate-packages‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ printf(
4343
PHP_EOL
4444
);
4545

46+
if ($packageGenerator->getMarkdownFile() !== null) {
47+
printf('Markdown listing written to %s%s', $packageGenerator->getMarkdownFile(), PHP_EOL);
48+
}
49+
4650
if ($report['skipped'] !== []) {
4751
printf('Skipped by ignoreRepos: %s%s', implode(', ', $report['skipped']), PHP_EOL);
4852
}

‎config/autoload/packages.global.php‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
* Dotkernel packages listing.
55
*
66
* Consumed by `bin/generate-packages`, which queries the GitHub organisation and writes the
7-
* result to `dataFile`. Credentials are intentionally NOT stored here - the GitHub token lives
8-
* in `config/autoload/local.php` under the `github` key, which is ignored by git.
7+
* result to `dataFile`, and the same data to `markdownFile` for `llms.txt`/`llms-full.txt` to
8+
* pick up. Credentials are intentionally NOT stored here - the GitHub token lives in
9+
* `config/autoload/local.php` under the `github` key, which is ignored by git.
910
*
1011
* `local.php` is merged after every `*.global.php`, so any value below can be overridden locally.
1112
*/
@@ -70,6 +71,7 @@
7071
'zf1',
7172
],
7273
'dataFile' => __DIR__ . '/../../public/dotkernel-packages.json',
74+
'markdownFile' => __DIR__ . '/../../public/dotkernel-packages-oss-lifecycle.md',
7375
'includeArchived' => true,
7476
'timeout' => 10,
7577
'connectTimeout' => 5,

‎public/dotkernel-packages-oss-lifecycle.md‎

Whitespace-only changes.

‎public/md-articles/best-practice/sql-queries-using-zend-db-select.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: "SQL queries using Zend_Db – SELECT"
2+
title: "SQL queries using Zend_Db - SELECT"
33
description: "How to write SELECT queries with JOINs and WHERE IN clauses using Zend_Db, alongside their equivalent raw SQL."
44
author: "Teo"
55
date_published: "2010-06-15"
@@ -8,7 +8,7 @@ category: "Best Practice"
88
language: "en"
99
---
1010

11-
# SQL queries using Zend_Db – SELECT
11+
# SQL queries using Zend_Db - SELECT
1212

1313
## TL;DR
1414

‎src/App/src/Factory/PackageGeneratorFactory.php‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use function assert;
1212
use function is_array;
1313
use function is_scalar;
14+
use function is_string;
1415

1516
class PackageGeneratorFactory
1617
{
@@ -39,12 +40,18 @@ public function __invoke(ContainerInterface $container): PackageGenerator
3940
}
4041
}
4142

43+
$markdownFile = isset($packages['markdownFile']) && is_string($packages['markdownFile'])
44+
? $packages['markdownFile']
45+
: null;
46+
4247
return new PackageGenerator(
4348
$client,
4449
(string) ($packages['dataFile'] ?? 'data/dotkernel-packages.json'),
4550
(string) ($github['org'] ?? 'dotkernel'),
4651
$ignoreRepos,
4752
(bool) ($packages['includeArchived'] ?? true),
53+
$markdownFile,
54+
(string) ($config['application']['url'] ?? ''),
4855
);
4956
}
5057
}

‎src/App/src/Fixture/articles_cleaned.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2704,7 +2704,7 @@
27042704
]
27052705
},
27062706
{
2707-
"name": "Architecture",
2707+
"name": "Architecture Deep Dive",
27082708
"slug": "architecture",
27092709
"isVisible": true,
27102710
"articles": [

‎src/App/src/Service/LlmsGenerator.php‎

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use function file_put_contents;
1717
use function glob;
1818
use function implode;
19+
use function in_array;
1920
use function preg_match;
2021
use function sprintf;
2122
use 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

Comments
 (0)