diff --git a/docs/book/v1/package-status.md b/docs/book/v1/package-status.md new file mode 100644 index 0000000..d83b4af --- /dev/null +++ b/docs/book/v1/package-status.md @@ -0,0 +1,90 @@ +# Dotkernel packages lifecycle page + +The page lists Dotkernel's open-source packages with their support status. +It renders a pre-generated JSON file, so the request path never calls GitHub. + +## Where this lives + +- Published page: +- Data file: +- Source repository: + +Every path below is relative to the root of `dotkernel/dotkernel.com`. +If the current working directory is a different project, the change belongs in that repository, not here - clone it or switch to that checkout before editing. + +## The pieces + +| Concern | Path in `dotkernel/dotkernel.com` | +|---------------------|---------------------------------------------------------------------------| +| Route | `src/App/src/RoutesDelegator.php` | +| Handler (read-only) | `src/App/src/Handler/GetPackagesViewHandler.php` | +| Template | `src/Blog/templates/page/dotkernel-packages-oss-lifecycle.html.twig` | +| Card styles | `src/App/assets/scss/components/_custom.scss` (`Package lifecycle cards`) | +| Generator | `src/App/src/Service/PackageGenerator.php` | +| GitHub transport | `src/App/src/Service/GitHubClient.php` | +| Config | `config/autoload/packages.global.php` | +| CLI entry point | `bin/generate-packages` | +| Data file | `public/dotkernel-packages.json` (gitignored) | + +Two rules follow from this split, and breaking either is the usual cause of a confusing result: + +- The handler only reads. +A change to the generator shows up on the page only after the data file is rebuilt. +- The data file is gitignored, so it does not travel with a deploy. +Production depends on the cron run. + +## How a repository gets onto the page + +A repository is a published package when it contains an `OSSMETADATA` file with an `osslifecycle` value. + +```text +osslifecycle=active +``` + +There is no allow-list to edit. +To keep a repository off the page, add its bare name (case-insensitive) to `ignoreRepos` in `config/autoload/packages.global.php`. + +Recognized values, which are also the display order (`LIFECYCLE_ORDER`): `active`, `maintenance`, `security-only`, `archived`. +Anything else is kept, sorted last, and rendered with the muted `package-card--unknown` treatment. +For equal lifecycles, the sorting is by repository name. + +Each entry in the data file is built from three sources: + +- `name`, `url`, `description`, `archived` - the `/orgs/{org}/repos` listing, so they cost no extra request. +- `lifecycle` - the repository's `OSSMETADATA`. +- `php` - `require.php` in the repository's `composer.json`; if no value is found, it defaults to `null`. + +`description` and `php` are `null` for anything unusable rather than an empty string, and the template omits the element instead of rendering a blank one. + +## Regenerating + +From the root of a `dotkernel/dotkernel.com` checkout: + +```bash +php bin/generate-packages +``` + +Credentials are saved in `config/autoload/local.php` under the `github` key (`authBearer`, `userAgent`, `org`) - see `local.php.dist`. +If no token is set, unauthenticated requests still work but have a much lower rate limit. + +The run is deliberately conservative: it writes to a `.tmp` sibling and renames, and it aborts with exit code 1 if more than 20% of per-repository requests fail (`FAILURE_THRESHOLD`), leaving the previous listing in place. +A non-zero exit means the page keeps serving yesterday's data, which is the intended outcome. + +## Reading the output + +- `Skipped by ignoreRepos: …` - informational, those repositories were excluded on purpose. +- `WARNING: ignoreRepos entries matched nothing (renamed or deleted?)` - stale `ignoreRepos` entries. +Prune them, or a renamed repository silently reappears on the site. +- `WARNING: : could not read OSSMETADATA (…)` - the request failed and counts toward the 20% threshold. +The repository is left off this run. +- `WARNING: : OSSMETADATA present but no osslifecycle value found, skipped` - the file exists but has no parsable `osslifecycle=`; fix it in that repository. +- `WARNING: : could not read composer.json (…)` - costs the PHP badge constraint, not the package. + +## After changing anything in the page + +- If the payload changed shape, rerun `php bin/generate-packages`, since the local data file predates the change. +- If the SCSS is updated, run `npm run build`. +`public/css/app.css` is tracked, so the compiled file must be committed with the source. +- Run the test battery using `vendor/bin/phpunit`, `vendor/bin/phpcs`, and `vendor/bin/phpstan analyse`. +Coverage lives in `test/Unit/App/Service/PackageGeneratorTest.php`, `test/Unit/App/Factory/PackageGeneratorFactoryTest.php`, and `test/Unit/App/Handler/GetPackagesViewHandlerTest.php`. +- If the data file is renamed, update `packages.global.php`, `.gitignore`, the "View as JSON" link in the template, and the fallback default in `src/App/src/Factory/PackageGeneratorFactory.php`.