Builder
laravel-package
Creates distributable Laravel packages for Packagist
Overview
The laravel-package agent creates distributable Laravel packages ready for Packagist publication. It scaffolds the package structure, configures autoloading, creates service providers, sets up testing, and generates documentation following community standards.
Responsibilities
- Package Scaffolding - Complete package directory structure
- Service Provider - Auto-discovery and configuration publishing
- Composer Setup - Autoloading, dependencies, scripts
- Testing Setup - Orchestra Testbench configuration
- Documentation - README, changelog, contributing guide
- CI/CD - GitHub Actions for testing and publishing
Package Structure
packages/vendor-name/package-name/
├── .github/
│ └── workflows/
│ ├── tests.yml
│ └── release.yml
├── config/
│ └── package-name.php
├── database/
│ └── migrations/
├── resources/
│ └── views/
├── src/
│ ├── Commands/
│ ├── Facades/
│ ├── Http/
│ │ ├── Controllers/
│ │ └── Middleware/
│ ├── Models/
│ ├── PackageNameServiceProvider.php
│ └── PackageName.php
├── tests/
│ ├── Feature/
│ ├── Unit/
│ └── TestCase.php
├── .gitignore
├── CHANGELOG.md
├── composer.json
├── LICENSE.md
├── phpunit.xml
└── README.md
Generated Service Provider
<?php
namespace VendorName\PackageName;
use Illuminate\Support\ServiceProvider;
class PackageNameServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(
__DIR__ . '/../config/package-name.php',
'package-name'
);
$this->app->singleton('package-name', function ($app) {
return new PackageName($app['config']['package-name']);
});
}
public function boot(): void
{
// Publish config
$this->publishes([
__DIR__ . '/../config/package-name.php' => config_path('package-name.php'),
], 'package-name-config');
// Publish migrations
$this->publishes([
__DIR__ . '/../database/migrations/' => database_path('migrations'),
], 'package-name-migrations');
// Load migrations
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
// Publish views
$this->loadViewsFrom(__DIR__ . '/../resources/views', 'package-name');
$this->publishes([
__DIR__ . '/../resources/views' => resource_path('views/vendor/package-name'),
], 'package-name-views');
// Register commands
if ($this->app->runningInConsole()) {
$this->commands([
Commands\InstallCommand::class,
Commands\PublishCommand::class,
]);
}
// Register routes
$this->loadRoutesFrom(__DIR__ . '/../routes/web.php');
}
}
Generated Facade
<?php
namespace VendorName\PackageName\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @method static mixed doSomething(string $param)
* @method static self configure(array $options)
*
* @see \VendorName\PackageName\PackageName
*/
class PackageName extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'package-name';
}
}
Composer Configuration
{
"name": "vendor-name/package-name",
"description": "A Laravel package that does something awesome",
"keywords": ["laravel", "package"],
"license": "MIT",
"authors": [
{
"name": "Your Name",
"email": "you@example.com"
}
],
"require": {
"php": "^8.2",
"illuminate/support": "^11.0"
},
"require-dev": {
"orchestra/testbench": "^9.0",
"pestphp/pest": "^2.0",
"laravel/pint": "^1.0"
},
"autoload": {
"psr-4": {
"VendorName\\PackageName\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"VendorName\\PackageName\\Tests\\": "tests/"
}
},
"scripts": {
"test": "vendor/bin/pest",
"format": "vendor/bin/pint"
},
"extra": {
"laravel": {
"providers": [
"VendorName\\PackageName\\PackageNameServiceProvider"
],
"aliases": {
"PackageName": "VendorName\\PackageName\\Facades\\PackageName"
}
}
},
"minimum-stability": "stable"
}
Test Setup with Orchestra Testbench
<?php
namespace VendorName\PackageName\Tests;
use Orchestra\Testbench\TestCase as Orchestra;
use VendorName\PackageName\PackageNameServiceProvider;
class TestCase extends Orchestra
{
protected function setUp(): void
{
parent::setUp();
// Run package migrations
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
}
protected function getPackageProviders($app): array
{
return [
PackageNameServiceProvider::class,
];
}
protected function getPackageAliases($app): array
{
return [
'PackageName' => \VendorName\PackageName\Facades\PackageName::class,
];
}
protected function defineEnvironment($app): void
{
$app['config']->set('database.default', 'testing');
$app['config']->set('database.connections.testing', [
'driver' => 'sqlite',
'database' => ':memory:',
]);
}
}
// tests/Feature/ExampleTest.php
use VendorName\PackageName\Facades\PackageName;
it('can do something', function () {
$result = PackageName::doSomething('test');
expect($result)->toBe('expected');
});
GitHub Actions CI
# .github/workflows/tests.yml
name: Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
php: [8.2, 8.3]
laravel: [11.*]
dependency-version: [prefer-lowest, prefer-stable]
name: PHP $ - Laravel $ - $
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: $
extensions: dom, curl, libxml, mbstring, zip
coverage: none
- name: Install dependencies
run: |
composer require "laravel/framework:$" --no-interaction --no-update
composer update --$ --prefer-dist --no-interaction
- name: Run tests
run: vendor/bin/pest
Install Command
<?php
namespace VendorName\PackageName\Commands;
use Illuminate\Console\Command;
class InstallCommand extends Command
{
protected $signature = 'package-name:install';
protected $description = 'Install the PackageName package';
public function handle(): int
{
$this->info('Installing PackageName...');
// Publish config
$this->call('vendor:publish', [
'--tag' => 'package-name-config',
]);
// Publish migrations
if ($this->confirm('Would you like to publish migrations?', true)) {
$this->call('vendor:publish', [
'--tag' => 'package-name-migrations',
]);
if ($this->confirm('Run migrations now?', true)) {
$this->call('migrate');
}
}
$this->info('PackageName installed successfully!');
return self::SUCCESS;
}
}
Local Development Setup
// In your Laravel app's composer.json
{
"repositories": [
{
"type": "path",
"url": "./packages/vendor-name/package-name"
}
],
"require": {
"vendor-name/package-name": "*"
}
}
// Then run:
// composer update vendor-name/package-name
Invoked By Commands
- laravel-architect - When extracting to package
Guardrails
The package agent follows strict rules:
- ALWAYS use PSR-4 autoloading
- ALWAYS include comprehensive tests with Testbench
- ALWAYS support Laravel auto-discovery
- NEVER hardcode application-specific dependencies
- NEVER publish to Packagist without tests passing
See Also
- laravel-module-builder - Internal modules
- laravel-cicd - CI/CD setup
- laravel-testing - Test generation