laranail / package-tools
Runtime base library for building Laravel packages — fluent Package builder + abstract PackageServiceProvider with attribute-driven discovery, package-tools.doctor, and isolation testing.
Requires
- php: ^8.4.1 || ^8.5
- illuminate/console: ^13.0
- illuminate/contracts: ^13.0
- illuminate/database: ^13.0
- illuminate/filesystem: ^13.0
- illuminate/http: ^13.0
- illuminate/support: ^13.0
- laranail/console: ^1.0
- symfony/process: ^8.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- mockery/mockery: ^1.6
- orchestra/testbench: ^11.0
- pestphp/pest: ^3.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
- rector/rector: ^2.0
- roave/security-advisories: dev-latest
- spatie/pest-plugin-test-time: ^2.2
README
Runtime base library for building Laravel packages.
A fluent Package builder and an abstract PackageServiceProvider your
package extends, in the spirit of spatie/laravel-package-tools. On top of
that base it adds attribute-driven discovery, a set of package-tools.*
Artisan commands, abstract HTTP controllers, and a testing harness.
Status: v1.2.0 — namespaced config with publishable overrides, a consolidated fluent builder (config/views/components/assets/routes/middleware/events/commands/seeders) with singular and array-batch forms, publishFile()/publishDirectory(), attribute-driven discovery, abstract HTTP controllers, a testing harness, four Artisan commands, and runtime services.
Contents
- Requirements
- Installation
- Quick start
- Declarative registration & batch helpers
- What you get
- Artisan commands
- Attribute discovery
- Documentation
- Local development
- Provenance
- Sister packages
- Contributing & security
- License
Requirements
- PHP
^8.4.1(8.4 and 8.5 are supported; CI tests the full8.4 / 8.5matrix). The.1floor comes fromlaranail/console(a dependency), which requires PHP^8.4.1. - Laravel
^13.0 - For development: Pest
^3.0, Testbench^11.0, Larastan^3.0
Installation
composer require laranail/package-tools
The service provider is auto-discovered, so the package-tools.* commands are
available as soon as the package is installed.
Upgrading across a breaking release? See UPGRADING.md.
Quick start
Extend PackageServiceProvider and describe your package in
configurePackage():
use Simtabi\Laranail\Package\Tools\Package; use Simtabi\Laranail\Package\Tools\PackageServiceProvider; final class FooServiceProvider extends PackageServiceProvider { public function configurePackage(Package $package): void { $package ->name('vendor/foo') ->hasConfigFile() ->hasViews() ->hasMigration('create_foos_table') ->hasInstallCommand(fn ($command) => $command ->publishConfigFile() ->askToRunMigrations()) ->discoversWithAttributes() ->hasDoctorCheck(FooHealthCheck::class); } }
A complete, runnable example lives in
docs/examples/.
Declarative registration & batch helpers
Beyond the core builder, Package exposes hooks for child providers, validation
rules, about sections, doctor checks, and namespaced asset publishing — each
with a batch sibling so you pass one array instead of chaining repeated calls:
$package ->name('vendor/foo') // Config files — the single call already accepts an array. ->hasConfigFile(['foo', 'foo-features']) // Register child / feature service providers (array form). ->hasChildProviders([Bar\BarServiceProvider::class, Baz\BazServiceProvider::class]) // Opt-in route-middleware aliases (the canonical batch entry point). ->registerMiddlewareAliases(['foo.auth' => EnsureFoo::class]) // Custom validator rules: `name => Class` or `name => [Class, message]`. ->hasValidationRules([ 'strong_password' => [StrongPassword::class, 'The :attribute is too weak.'], ]) // `php artisan about` sections: label => callable. ->hasAboutSections(['Foo' => fn (): array => ['Version' => Foo::VERSION]]) // Doctor checks (batch). ->hasDoctorChecks([FooHealthCheck::class, BarHealthCheck::class]) // Publish an individual FILE or DIRECTORY under a namespaced `::` tag. ->publishFile('config/foo-data.php', config_path('foo-data.php'), 'data') // tag: vendor::foo-data ->publishDirectory('stubs', base_path('stubs/vendor/foo'), 'stubs'); // tag: vendor::foo-stubs
The singular forms (hasValidationRule(), hasAboutSection(), hasDoctorCheck(),
registerMiddlewareAlias(), sharesDataWithAllViews($name, $value)) remain for
one-off registration.
Testing published config overrides
A namespaced config publishes to a nested config/vendor/package.php that Laravel
never auto-loads; the provider bridges it back into the dotted key at register
time. To test that round-trip reliably (without racing Testbench's
getEnvironmentSetUp()), use the AssertsPublishedConfigOverrides trait — it's
already mixed into Testing\IsolatedTestCase:
use Simtabi\Laranail\Package\Tools\Testing\AssertsPublishedConfigOverrides; $this->assertPublishedConfigOverride( FooServiceProvider::class, 'vendor.foo', ['enabled' => false], 'vendor.foo.enabled', false, );
What you get
| Capability | Summary |
|---|---|
Fluent Package builder |
Spatie-compatible surface: name(), hasConfigFile(), hasViews(), hasViewComponents(), hasInertiaComponents(), hasViewComposer(), sharesDataWithAllViews(), hasTranslations(), hasAssets(), hasRoute(), hasMigration(), runsMigrations(), discoversMigrations(), hasCommand(), hasInstallCommand(), publishesServiceProvider(). |
| Attribute discovery | discoversWithAttributes() scans src/ for #[AsArtisanCommand], #[AsRoute], and #[AsViewComposer] and registers them for you. |
| Namespaced config | hasNestedConfig() / discoversConfig() mount config sub-folders at dotted keys (config/admin/panel.php → config('admin.panel.*')), with an optional in-file __namespace. See config-namespacing. |
| Artisan commands | laranail::package-tools.doctor, .sbom, .audit, .ide-helper (see below). |
| HTTP controllers | Optional WebController / ApiController base classes for package routes. |
| Testing harness | Testing\IsolatedTestCase — Testbench wrapper with in-memory SQLite, sync queue, and schema/command assertions. |
| Runtime services | SystemService, HttpConfigurationService, and ErrorStorageService, bound by the provider and resolvable from the container. |
The full reference is in docs/ — see Documentation.
Artisan commands
Registered automatically via package auto-discovery.
| Command | Purpose | Options |
|---|---|---|
php artisan laranail::package-tools.doctor |
Run every registered DoctorCheck; non-zero exit on any failure. |
--json, --strict |
php artisan laranail::package-tools.sbom |
Emit a CycloneDX 1.5 JSON SBOM for composer.lock. |
--output=, --print |
php artisan laranail::package-tools.audit |
Query OSV.dev for advisories in composer.lock; non-zero exit on any advisory. |
--no-dev, --json, --timeout= |
php artisan laranail::package-tools.ide-helper |
Generate Facade classes from #[AsFacade] contracts with @method docblocks. |
--source=, --output= |
Commands follow the laranail::<package-slug>.<command> naming used across the
laranail family (the :: separator is enabled by the package's base command —
see CONTRIBUTING.md).
Attribute discovery
use Simtabi\Laranail\Package\Tools\Attributes\AsArtisanCommand; use Simtabi\Laranail\Package\Tools\Attributes\AsRoute; use Simtabi\Laranail\Package\Tools\Attributes\AsViewComposer; use Simtabi\Laranail\Package\Tools\Attributes\AsFacade; #[AsArtisanCommand(signature: 'foo:run', description: 'Run the foo task')] class FooCommand extends Command {} #[AsRoute(method: 'GET', uri: '/foo')] #[AsRoute(method: 'POST', uri: '/foo', name: 'foo.create', middleware: ['web'])] class FooController {} #[AsViewComposer(views: ['layouts.app', 'partials.header'])] class AppViewComposer {}
See docs/tools/attribute-discovery.md.
Documentation
Hosted at opensource.simtabi.com/package-tools/docs/
(product page: opensource.simtabi.com/package-tools/).
The same pages live under docs/:
Guides
- Installation — requirements, install, auto-registration
- Configuration — the fluent
Packagebuilder and lifecycle hooks - Seeding — package + standalone seeding: registry, executor, fluent builder,
SeederExecutionStats, events, console output - Architecture — how the runtime is put together
- Services reference — the service and support classes
Tools & features
- package-tools.doctor — health checks
- package-tools.sbom — CycloneDX SBOM
- package-tools.audit — OSV.dev advisory scan
- package-tools.ide-helper — Facade generation from
#[AsFacade] - Attribute discovery —
#[AsArtisanCommand],#[AsRoute],#[AsViewComposer] - Namespaced config — folder-tree config keys (
config('admin.panel.*')) + the optional__namespace - Command naming — the
laranail::<slug>.<command>separator, the baseCommand, andSupportsNamespacedNames - HTTP controllers —
WebControllerandApiController - IsolatedTestCase — the testing harness
- Runtime services —
SystemService,HttpConfigurationService,ErrorStorageService
Examples
- Runnable examples — a cohesive
Acme\Hellopackage that demonstrates every feature end to end: the fluent builder and lifecycle hooks, all four discovery attributes, namespaced commands, theWebController/ApiControllerbases, doctor checks, the runtime services and consumer traits, package seeders, the install-command flow, and anIsolatedTestCasetest
Local development
bash .scripts/init.sh # verify PHP >= 8.4, run composer install, smoke-check composer setup # alias for .scripts/init.sh composer test # run the Pest suite composer test-coverage # run with coverage composer lint # Pint + PHPStan + Rector (dry run) composer pint-fix # apply Pint formatting composer rector-fix # apply Rector transformations composer audit # composer security audit
.scripts/init.sh is the only shell script in the repo; everything else runs
through Composer.
Provenance
The open API surface (hasConfigFile(), hasViews(), the lifecycle hooks,
and friends) is intentionally compatible with
spatie/laravel-package-tools
(MIT). Code is copyright Simtabi LLC; see LICENSE.
Sister packages
laranail/package-scaffolder— Artisan generator that scaffolds new packages on top ofpackage-tools.laranail/database-tools— standalone Laravel database utilities; no dependency onpackage-tools.laranail/laranail— Simtabi's Laravel utility toolbox.
Contributing & security
- CONTRIBUTING.md — workflow, coding standards, command naming.
- SECURITY.md — how to report a vulnerability.
- CODE_OF_CONDUCT.md — community expectations.
- CHANGELOG.md — release history.
License
MIT. See LICENSE.