schenke-io / packaging-tools
Tools to simplify publishing github packages
Requires
- php: ^8.2 || ^8.3 || ^8.4
- ext-curl: *
- ext-gd: *
- ext-json: *
- ext-simplexml: *
- badges/poser: ^3.1
- illuminate/filesystem: ^12.0 || ^13.0
- illuminate/http: ^12.0 || ^13.0
- kitloong/laravel-migrations-generator: ^7.0
- nette/neon: ^3.4
- nette/php-generator: ^4.2
- nette/schema: ^1.3
- symfony/console: ^7.1
Requires (Dev)
- larastan/larastan: ^3.5
- laravel/pint: ^1.24
- mockery/mockery: ^1.5
- pestphp/pest: ^4.2
- pestphp/pest-plugin-mutate: ^4.0
- phpstan/phpstan-phpunit: ^2.0
- phpunit/phpunit: ^12.3
- spatie/ray: ^1.40
README
Packaging Tools
Tools to simplify publishing github packages
This package is a collection of tools to simplify the package and project development.
The main elements are:
- Markdown Assemble the readme.md file out of small markdown files, class comments and other sources
- Badge build the badge custom or from existing files
- Setup read the
.packaging-tools.neonconfiguration file and modify scripts incomposer.json - Boost Skills & Guidelines this package supports the Laravel Boost standard for AI-ready packages.
Skill-based documentation
Documentation for this package is partly generated from AI Skills. These skills are small, focused pieces of documentation that describe specific features or workflows. They are located in resources/boost/skills/ and are also compatible with Laravel Boost.
By using these skills, the package ensures that both human developers and AI assistants have clear, actionable instructions on how to use the provided tools.
- Packaging Tools
| Title | Description |
|---|---|
| packaging-tools-badges | Generate SVG badges for project metrics |
| packaging-tools-guidelines | Write AI guidelines and skills for projects based on Laravel Boost standards |
| packaging-tools-imported-migrations | Regenerate package migrations from a live database schema |
| packaging-tools-markdown-assembly | Assemble modular documentation and class references into a README |
| packaging-tools-setup | Install, configure, and run packaging tools via composer scripts |
| packaging-tools-speed-seeding | Speed up tests by loading a pre-generated SQL dump instead of running migrations |
Badges
When to use this skill
Use when you need to generate or update SVG badge files for a project's README, or when adding badge configuration to a Markdown assembler script.
Quick start
Generate all auto-detected badges at once:
composer setup badges
This calls MakeBadge::auto(), which scans for known source files (clover.xml, phpstan.neon, infection-report.json, composer.json) and writes SVG files to resources/md/svg/ (or workbench/resources/md/svg/).
Auto-detected badge types
| Badge | Source | Detection |
|---|---|---|
| Coverage | clover.xml | path from phpunit.xml |
| PhpStan | phpstan.neon / phpstan.neon.dist | auto-discovered |
| Infection | infection-report.json | auto-discovered |
| PHP version | composer.json | require.php constraint |
| Latest version | composer.json | package name → Packagist |
| Downloads | composer.json | package name → Packagist |
| Laravel version | composer.json | require.laravel/framework |
| Tests | composer.json | GitHub repo → Actions |
| License | composer.json | license field |
PHP API
use SchenkeIo\PackagingTools\Badges\MakeBadge; // auto-generate all detected badges MakeBadge::auto(); // generate specific badges (path optional, auto-detected when omitted) MakeBadge::makeCoverageBadge(); // from clover.xml MakeBadge::makePhpStanBadge(color: '2563eb'); // blue by default MakeBadge::makeInfectionBadge(); MakeBadge::makePhpVersionBadge(); // with explicit paths MakeBadge::makeCoverageBadge('build/logs/clover.xml'); MakeBadge::makePhpStanBadge('phpstan.neon', '16a34a'); // custom badge MakeBadge::define('My Label', 'passing', '27AE60') ->store('resources/md/svg/my-badge.svg');
store() signature: store(?string $filepath = null, ?BadgeStyle $style = null). When $filepath is null, the badge is stored at $markdownDir/svg/{subject-slug}.svg.
Badge styles
use SchenkeIo\PackagingTools\Enums\BadgeStyle; BadgeStyle::Flat // flat (default) BadgeStyle::FlatSquare // flat-square BadgeStyle::Plastic // plastic BadgeStyle::ForTheBadge // for-the-badge
Forge badge (requires explicit parameters)
The Forge deployment badge is not auto-detected and must be added via the Markdown assembler:
$assembler->badges()->forge( hash: 'your-hash', server: 123456, site: 654321, date: 1, // 1 = show date, 0 = hide label: 1 // 1 = show label, 0 = hide );
AI Guidelines and Skills for Boost
When to use this skill
Use when creating or updating AI guidelines (core.blade.php) or skill files (SKILL.md) for a Laravel package so that Boost-compatible projects can load them automatically.
AI Guidelines
Add a resources/boost/guidelines/core.blade.php file to your package. When users run php artisan boost:install, Boost loads it automatically as AI context.
Guidelines should:
- Briefly describe what the package does
- List key conventions and file structures
- Show example commands and code snippets
- Be concise and actionable — written for AI, not humans
Example core.blade.php
## vendor/package-name This package provides [brief description]. ### Features - Feature 1: [description]. - Feature 2: [description]. Example: ```php $result = PackageName::featureTwo($param1, $param2);
The `@verbatim` / `<code-snippet>` wrapper is processed by the Skills assembler into a plain fenced code block when included in Markdown output.
### <a name="ai-skills"></a>AI Skills
Add a `resources/boost/skills/{skill-name}/SKILL.md` file. Required frontmatter: `name` and `description`. The folder name is used as the skill identifier.
#### <a name="example-skill-md"></a>Example `SKILL.md`
```markdown
---
name: package-name-feature
description: Build and work with PackageName features.
---
## When to use this skill
Use when working with PackageName features...
## Features
- Feature 1: description.
- Feature 2: description. Example:
$result = PackageName::featureTwo($param1, $param2);
Including skills in Markdown assembly
The MarkdownAssembler can embed skill content or render a summary table:
use SchenkeIo\PackagingTools\Markdown\MarkdownAssembler; $assembler = new MarkdownAssembler('resources/md'); // embed full content of all skills $assembler->skills()->all(); // embed a specific skill $assembler->skills()->add('my-skill-name'); // render a summary table (name + description) for all skills $assembler->skillOverview();
skillOverview() generates a Markdown table with linked skill names and their descriptions, sourced from each SKILL.md's frontmatter.
Database Migrations
When to use this skill
Use when you want to keep your package's migration files in sync with a development database. This is a "database-first" workflow: you modify the database manually, then regenerate the migrations from it.
Requires kitloong/laravel-migrations-generator to be installed.
Quick start
composer migrations
This runs the full migration regeneration cycle: deletes old migrations, generates new ones from the configured database connection, then cleans environment-specific connection calls from the files.
Configuration (.packaging-tools.neon)
migrations: mysql:*
Format: connection:tables — use * to auto-detect tables from your Eloquent models.
Examples:
mysql:*— usemysqlconnection, detect tables from modelssqlite:users,posts— usesqliteconnection, only those tablesnull— disable migration generation
Process
- Checks that
kitloong/laravel-migrations-generatoris installed. - Reads the connection and table list from
.packaging-tools.neon. - If
*is used, scans for Eloquent models in (in priority order):workbench/app/Modelsapp/Modelssrc/Models
- Cleans the migrations folder.
- Runs
migrate:generatewith--skip-log --default-index-names --date=2020-10-10. - Strips environment-specific
connection()calls from generated files. - Sets generated files to read-only (mode 444) to prevent manual edits.
Workbench support
If workbench/database/migrations exists, it is used as the migration output path instead of database/migrations.
Using the trait in an Artisan command
use SchenkeIo\PackagingTools\Traits\GeneratesPackageMigrations; use Illuminate\Console\Command; class MyMigrationCommand extends Command { use GeneratesPackageMigrations; public function handle(): void { $this->generatePackageMigrations(); } }
The trait must be used inside a class that extends Illuminate\Console\Command (it calls $this->call()). It reads .packaging-tools.neon automatically and performs the same steps as composer migrations.
Markdown Assembler
When to use this skill
Use when building or editing the script that generates README.md from modular source files. The assembler compiles badges, markdown partials, class docs, tables, and a table of contents into a single output file.
Script location
The assembler script is auto-generated by composer setup. Depending on the project structure:
- Package with workbench:
workbench/app/Console/Commands/MakeMarkdown.php(Laravel console command) - Standalone package:
.make-markdown.php(plain PHP script, run viaphp .make-markdown.php)
Initialize source markdown files (creates resources/md/ with placeholder files):
MarkdownAssembler::init('resources/md');
Minimal example
use SchenkeIo\PackagingTools\Markdown\MarkdownAssembler; $assembler = new MarkdownAssembler('resources/md'); $assembler ->addTableOfContents() ->addMarkdown('header.md') ->badges()->all() ->addMarkdown('installation.md') ->addMarkdown('usage.md') ->classes()->all() ->writeMarkdown('README.md');
MarkdownAssembler takes the markdown source directory as its first argument. All addMarkdown() paths are relative to that directory.
Assembler methods
| Method | Purpose |
|---|---|
addTableOfContents() |
Inserts a linked TOC (auto-generated from all headings) |
addMarkdown(string $file) |
Includes a markdown file from the source directory |
addText(string $content) |
Inlines raw markdown text |
autoHeader(?string $title) |
Auto-generates title, description, cover image, and all badges |
skipWrittenBy() |
Omits the "DO NOT EDIT" header comment and footer |
image(string $alt, string $path, string $url) |
Adds a linked image |
addContentProvider(MarkdownPieceInterface $provider) |
Adds a custom content block |
writeMarkdown(string $filepath) |
Assembles and writes the final file |
Badges: badges()
$assembler->badges()->all(); // all auto-detected badges $assembler->badges()->version(BadgeStyle::Flat); $assembler->badges()->test('run-tests.yml'); $assembler->badges()->download(BadgeStyle::FlatSquare); $assembler->badges()->php(); $assembler->badges()->local('My Badge', 'resources/md/svg/my-badge.svg'); $assembler->badges()->forge(hash: 'abc', server: 1, site: 2, date: 1, label: 1);
all() automatically includes local SVG badges from $markdownDir/svg/ and remote badges (version, downloads, tests, php) detected from composer.json.
Classes: classes()
Documents PHP classes by extracting PHPDoc and method signatures:
$assembler->classes()->all(); // all classes in src/ $assembler->classes()->add(MyClass::class); // single class $assembler->classes()->glob('src/Models/*.php'); // glob pattern $assembler->classes()->custom(MyClass::class, fn($data) => "## {$data->name}");
Only classes with @markdown in their PHPDoc or significant documentation are rendered.
Tables: tables()
$assembler->tables()->fromFile('data/table.csv'); $assembler->tables()->fromCsvString($csv, ','); $assembler->tables()->fromArray([['Col1', 'Col2'], ['a', 'b']]);
Skills: skills()
$assembler->skills()->all(); // all skills in resources/boost/skills/ $assembler->skills()->add('my-skill-name'); // specific skill $assembler->skillOverview(); // summary table (name + description)
Table of Contents
The TOC is auto-built from all # headings across all assembled blocks. Use addTableOfContents() to place a placeholder; it is replaced with the full linked TOC at write time. Heading anchors (<a name="...">) are injected automatically.
Setup
When to use this skill
Use when installing the package for the first time, updating the configuration, or running any of the built-in task commands (test, analyse, coverage, markdown, etc.).
Installation
composer require schenke-io/packaging-tools
Add the setup script to composer.json:
{
"scripts": {
"setup": "SchenkeIo\\PackagingTools\\Setup::handle"
}
}
Commands
| Command | Action |
|---|---|
composer setup |
Show current config status and any pending changes |
composer setup config |
Create or sync .packaging-tools.neon |
composer setup update |
Apply pending composer.json script additions and install missing packages |
composer setup badges |
Generate all auto-detected SVG badges |
composer setup <task> |
Run a specific configured task (e.g., test, pint, quick) |
Configuration file: .packaging-tools.neon
analyse: true coverage: true infection: true markdown: php .make-markdown.php migrations: mysql:* pint: true test: pest quick: - pint - test - markdown release: - pint - analyse - coverage - infection - markdown sql-cache: true customTasks: {}
Configuration keys
| Key | Type | Description |
|---|---|---|
analyse |
bool |
PHPStan static analysis (auto-detects Larastan) |
coverage |
bool |
Adds coverage flags to test run; requires clover.xml |
infection |
bool |
Mutation testing via infection/infection |
markdown |
string|null |
Command to run the Markdown assembler script |
migrations |
string|null |
Migration regeneration: format connection:tables or connection:* |
pint |
bool |
Code style with Laravel Pint |
test |
string |
Test runner: pest, phpunit, or '' to disable |
quick |
array |
Task group for fast iteration (default: pint, test, markdown) |
release |
array |
Task group for pre-release checks |
sql-cache |
bool|string|null |
Dump SQLite DB to SQL file; true = tests/Data/seeded.sql |
customTasks |
array |
Map of custom task names to shell commands or class names |
Custom tasks
customTasks: lint: php -l src/ my-task: App\Console\Commands\MyTask
Run with: composer setup lint or composer setup my-task.
Concept
- Configuration is the single source of truth — editing
.packaging-tools.neoncontrols everything. - Manual edits to
composer.jsonscripts are preserved; the tool warns before overwriting. composer setup(no args) shows a diff of what would change — it never modifies without an explicit subcommand.- All keys define their own schema via
nette/schema; invalid config produces a clear error with a "did you mean?" suggestion.
Speed Seeding
When to use this skill
Use in test suites where running full migrations and seeders on every test is too slow. Load a pre-generated SQL dump once per test run instead.
This is a companion to the sql-cache configuration key, which generates the dump.
How it works
- Generate the dump —
sql-cache: truein.packaging-tools.neonmakescomposer setupdump the current SQLite DB totests/Data/seeded.sql(configurable path). - Load in tests — the
LoadsSeededSqltrait loads that dump on first access, detected by checking if theuserstable exists.
Usage
use SchenkeIo\PackagingTools\Traits\LoadsSeededSql; use Illuminate\Foundation\Testing\DatabaseTransactions; use Tests\TestCase; class MyFeatureTest extends TestCase { use DatabaseTransactions; // wrap each test in a transaction — do NOT use RefreshDatabase use LoadsSeededSql; public function setUp(): void { parent::setUp(); $this->loadSeededSql(); // default: tests/Data/seeded.sql // $this->loadSeededSql('tests/Data/custom.sql'); // custom path } }
loadSeededSql() is a no-op if the users table already exists in the current connection, so it is safe to call in every test without redundant reloads.
Generating the SQL dump
In .packaging-tools.neon:
sql-cache: true # dumps to tests/Data/seeded.sql # sql-cache: tests/Data/custom.sql # custom path # sql-cache: null # disabled
To run migrations and then dump in one step, add @sql-cache as a step in a custom task:
customTasks: seed: "@sql-cache"
Important
- Use
DatabaseTransactions, notRefreshDatabase.RefreshDatabasere-runs migrations and undoes the seeded state. - The SQL file must be committed to the repository so CI can load it without running seeders.
- Regenerate the dump whenever your schema or seed data changes.
| key | description |
|---|---|
| analyse | false = disabled, true = enabled (uses phpstan/phpstan-phpunit or larastan/larastan) |
| coverage | false = disabled, true = enabled (adds --coverage to the test runner) |
| infection | false = disabled, true = enabled (requires infection/infection) |
| markdown | null = disabled, string = enabled (command to assemble Markdown files) |
| migrations | null = disabled, connection:* = auto-detect, connection:table1,table2 = enabled (with connection and tables) |
| pint | false = disabled, true = enabled (uses laravel/pint) |
| quick | an array of scripts to include in this group: pint, test, markdown |
| release | an array of scripts to include in this group: pint, analyse, test, coverage, infection, markdown |
| sql-cache | null = disabled, true = default path, 'path/to/file.sql' = custom path |
| test | '' = disabled, 'pest' or 'phpunit' = enabled |
MarkdownAssembler
Core engine for assembling Markdown documentation.
How to assemble a markdown
To assemble a markdown you need:
- a directory with markdown source files (e.g.,
workbench/resources/md) - an assembly script (e.g.,
workbench/MakeMarkdown.php)
The MarkdownAssembler helps you combine static markdown files with dynamically generated content like badges, tables, and class documentation.
Bootstrapping
You can initialize a markdown directory with standard files:
use SchenkeIo\PackagingTools\Markdown\MarkdownAssembler; MarkdownAssembler::init('workbench/resources/md');
Assembly Example
<?php require "vendor/autoload.php"; use SchenkeIo\PackagingTools\Markdown\MarkdownAssembler; try { $mda = new MarkdownAssembler('workbench/resources/md'); // add a header with project name, description and badges $mda->autoHeader('My Awesome Package'); // include a static file from the markdown directory $mda->addMarkdown("introduction.md"); // add a Table of Contents for all headings in the final document $mda->toc(); // add a table with all skills, their descriptions and links $mda->skillOverview(); // add all skills from resources/boost/skills/ $mda->skills()->all(); // add a table from a CSV file $mda->tables()->fromFile('data.csv'); // add documentation for all classes in src/ $mda->classes()->all(); // or from a single class $mda->classes()->add(MarkdownAssembler::class); // write the result to a file (relative to root directory) $mda->writeMarkdown("README.md"); } catch (Exception $e) { echo "ERROR: " . $e->getMessage() . PHP_EOL; }
Key Methods
autoHeader(?string $title): Adds project title, description, and common badges.addMarkdown(string $filename): Includes a file from the source directory.addText(string $text): Appends raw markdown text.toc(): Inserts a Table of Contents.skillOverview(): Adds a table with all skills, their descriptions and links.skills(): Accesses the Skills piece for including feature documentation.tables(): Accesses the Tables piece for creating markdown tables from arrays, CSV strings, or files.classes(): Accesses the Classes piece for generating documentation from PHP classes using reflection.badges(): Accesses the Badges piece for adding custom badges.writeMarkdown(string $filename): Finalizes and writes the assembled markdown to the specified path.
Public methods of MarkdownAssembler
| method | summary |
|---|---|
| init | - |
| skipWrittenBy | - |
| autoHeader | - |
| addMarkdown | Adds a markdown file. |
| addTableOfContents | add a table of content for the full file |
| addText | adds markdown text |
| addContentProvider | - |
| badges | - |
| classes | - |
| tables | - |
| toc | - |
| skills | - |
| skillOverview | Add a table with all skills, their descriptions and links. |
| image | - |
| writeMarkdown | writes all added elements into one file |
MakeBadge
Central class for generating and storing SVG badges.
Public methods of MakeBadge
| method | summary |
|---|---|
| auto | Automatically detect and generate all supported badge types. |
| define | Create a new MakeBadge instance with manual definitions. |
| fromDriver | Create a new MakeBadge instance using a driver. |
| makeCoverageBadge | Create a coverage badge from a clover.xml file. |
| makePhpStanBadge | Create a PHPStan badge from a neon configuration file and a specific color. |
| makeInfectionBadge | Create an Infection badge from a JSON report. |
| makePhpVersionBadge | Create a PHP version badge from composer.json. |
| info | Get the informational summary string for the badge. |
| store | Generate the SVG and store it in a file. |
Config
Handles the configuration for the packaging tools.
Public methods of Config
| method | summary |
|---|---|
| getMarkdownDir | returns the directory where Markdown source files are located |
| output | outputs a message to the console unless silent mode is active |
| doConfiguration | Entry point for configuration updates |
| getC2pDeltas | returns a list of deltas from composer.json to configuration |
| writeConfig | writes a default configuration file if it doesn't exist or merges deltas |
Markdown file generated by schenke-io/packaging-tools
