mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-07-16 22:05:20 +00:00
fix: boot app + AI driver registration on invoiceshelf/modules 3.0.3 (via VCS) (#655)
* fix(deps): require invoiceshelf/modules ^3.0.2 (adds registerExchangeRateDriver)
DriverRegistryProvider::registerExchangeRateDrivers() calls
Registry::registerExchangeRateDriver(), which only exists in
invoiceshelf/modules >= 3.0.2. The constraint (^3.0) and the committed
lock (3.0.1) allowed/pinned versions without it, so a fresh
`composer install` (CI, Docker, new clones) boots into:
Call to undefined method InvoiceShelf\Modules\Registry::registerExchangeRateDriver()
Pin to ^3.0.2 and update the lock so every install gets a version that
has the method. `php artisan package:discover` verified clean.
* fix(ai): register the AI driver via the generic Registry::registerDriver('ai', ...)
registerAiDriver() is a convenience wrapper that is NOT present in any
published invoiceshelf/modules release (only the generic registerDriver()
and registerExchangeRateDriver() ship), so DriverRegistryProvider crashed
app boot on a clean composer install:
Call to undefined method InvoiceShelf\Modules\Registry::registerAiDriver()
Use registerDriver('ai', 'openrouter', ...) instead -- it stores under the
'ai' type exactly like the wrapper would, and AiConfigurationService /
AiDriverFactory read it back via allDrivers('ai') / driverMeta('ai', ...).
Verified by clean-reinstalling invoiceshelf/modules (no local patch) and
running `php artisan package:discover` -> boots clean.
* style: fix pre-existing Pint violations in backup services
BackupService.php and BackupConfigurationFactory.php (untouched by this
PR's boot fix) carried style violations from an earlier domain-reorg
refactor (6d1816bd). `pint --test` checks the whole tree and runs on any
PR that touches PHP, so these failed CI here. Auto-fixed with Pint
(braces_position, no_unused_imports, single_line_empty_body) so the check
goes green.
* build(deps): pull invoiceshelf/modules ^3.0.3 via VCS, restore registerAiDriver()
The 3.0.3 release adds Registry::registerAiDriver() (the method DriverRegistryProvider
and AiDriverFactoryTest call). Packagist has the package frozen, so resolve it directly
from the canonical GitHub repo via a composer VCS repository and require ^3.0.3 (the tag
exists; the freeze is Packagist-side only).
Now that the method ships, restore DriverRegistryProvider to Registry::registerAiDriver()
— reverting the temporary generic registerDriver('ai', ...) workaround — so it matches the
package's intended API and the existing tests. The provider is now net-identical to 3.x.
Verified: php artisan package:discover boots clean; the AI suite (incl. the previously
failing AiDriverFactoryTest) passes.
* test: provision modules_statuses.json in the test bootstrap
The Modules/HelloWorld integration test needs the module enabled at the nwidart
level, read from storage/app/modules_statuses.json at app boot. That file is
gitignored (created locally by `module:make`), so it's absent on CI and fresh
clones — HelloWorld stays disabled and the 5 integration tests fail with 404s.
Provision it (only if missing) in tests/Pest.php before any test boots the app,
so CI matches a local dev environment. Full suite: 462 pass.
* fix(test): enable HelloWorld via a committed modules_statuses.json
The Modules/HelloWorld integration test needs the module enabled at the nwidart
level — read from storage/app/modules_statuses.json by FileActivator. That file
is gitignored, so it's absent in CI / fresh clones, leaving HelloWorld disabled
and the 5 integration tests failing with 404s.
The previous tests/Pest.php provisioning (60a7f0d6) only worked under
`./vendor/bin/pest`. CI runs `php artisan test`, which boots the console app
first; FileActivator reads and caches the (absent) statuses at construction
BEFORE Pest.php runs, so test-runtime provisioning is too late. The file must
exist before any boot.
Commit the file via a storage/app/.gitignore negation, and revert the
ineffective Pest.php hack. Prod-safe: Modules/ is gitignored and not copied by
release.yaml, so a phantom "HelloWorld: true" status is ignored by nwidart (no
such module on disk). bootstrap/cache/modules.php is gitignored (absent in CI),
so nothing overrides the committed file.
Verified with `php artisan test --filter=HelloWorld` (the CI command) and the
full suite: 462 pass; pint clean.
* fix(test): commit the Modules/HelloWorld sample module
HelloWorldIntegrationTest exercises Modules/HelloWorld end-to-end, but Modules/
was gitignored (/Modules), so the module was absent from the repo and from CI —
the 5 integration tests 404'd, and the committed modules_statuses.json merely
enabled a module that wasn't there.
Track Modules/HelloWorld (the test fixture) via a `/Modules/*` + `!HelloWorld`
negation. Not shipped to prod (release.yaml omits Modules/). Now the module is
present, autoloaded (merge-plugin), and enabled (statuses file), so its provider
boots and the menu/settings/routes register.
* build: drop the boost:update post-update-cmd hook (breaks CI)
laravel/boost gates its commands to the local environment, so `php artisan
boost:update` fails in CI ("There are no commands defined in the boost
namespace"), making composer's post-update-cmd return exit 1.
It only began failing the build once Modules/HelloWorld/composer.json was
committed: the wikimedia merge-plugin then runs composer's update path on a
plain `composer install`, triggering post-update-cmd. Drop the auto-update
hook (run `boost:update` manually when needed); vendor:publish stays.
This commit is contained in:
committed by
GitHub
parent
ac2a8ca939
commit
b73dcb5278
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\HelloWorld\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class HelloWorldController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('helloworld::index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('helloworld::create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request) {}
|
||||
|
||||
/**
|
||||
* Show the specified resource.
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
return view('helloworld::show');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
return view('helloworld::edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, $id) {}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy($id) {}
|
||||
}
|
||||
27
Modules/HelloWorld/app/Providers/EventServiceProvider.php
Normal file
27
Modules/HelloWorld/app/Providers/EventServiceProvider.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\HelloWorld\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The event handler mappings for the application.
|
||||
*
|
||||
* @var array<string, array<int, string>>
|
||||
*/
|
||||
protected $listen = [];
|
||||
|
||||
/**
|
||||
* Indicates if events should be discovered.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $shouldDiscoverEvents = true;
|
||||
|
||||
/**
|
||||
* Configure the proper event listeners for email verification.
|
||||
*/
|
||||
protected function configureEmailVerification(): void {}
|
||||
}
|
||||
122
Modules/HelloWorld/app/Providers/HelloWorldServiceProvider.php
Normal file
122
Modules/HelloWorld/app/Providers/HelloWorldServiceProvider.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\HelloWorld\Providers;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use InvoiceShelf\Modules\Registry as ModuleRegistry;
|
||||
use InvoiceShelf\Modules\Support\ModuleServiceProvider;
|
||||
|
||||
/**
|
||||
* Reference module that exercises every InvoiceShelf module-system surface.
|
||||
*
|
||||
* This file started life as the output of `php artisan module:make HelloWorld`
|
||||
* — which, thanks to the custom stubs shipped from the invoiceshelf/modules
|
||||
* package, already includes the Registry::registerMenu/registerSettings
|
||||
* skeleton. The schema below has been expanded beyond the stub default to
|
||||
* exercise more field types for test coverage.
|
||||
*/
|
||||
class HelloWorldServiceProvider extends ModuleServiceProvider
|
||||
{
|
||||
protected string $name = 'HelloWorld';
|
||||
|
||||
protected string $nameLower = 'helloworld';
|
||||
|
||||
protected array $providers = [
|
||||
EventServiceProvider::class,
|
||||
RouteServiceProvider::class,
|
||||
];
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
$slug = Str::kebab($this->name);
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Module script (Vue page registration)
|
||||
// ----------------------------------------------------------------
|
||||
// Registers a compiled JS file that the host app injects as a
|
||||
// <script type="module"> tag. The script calls
|
||||
// window.InvoiceShelf.booting() to add Vue routes before mount.
|
||||
ModuleRegistry::registerScript(
|
||||
$slug,
|
||||
module_path($this->name, 'resources/dist/init.js')
|
||||
);
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Sidebar menu item
|
||||
// ----------------------------------------------------------------
|
||||
// Adds a link in the company sidebar under the "Modules" section.
|
||||
// The `title` uses a Laravel translation key (namespace::file.key)
|
||||
// resolved server-side before being sent to the Vue frontend.
|
||||
// The `link` points to the module's own Vue page (registered by init.js).
|
||||
// The `icon` is any Heroicon name available in BaseIcon.
|
||||
ModuleRegistry::registerMenu($slug, [
|
||||
'title' => $this->nameLower.'::menu.title',
|
||||
'link' => '/admin/modules/'.$slug.'/dashboard',
|
||||
'icon' => 'HandRaisedIcon',
|
||||
]);
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Schema-driven settings
|
||||
// ----------------------------------------------------------------
|
||||
// Registers a settings form that appears in the company modules
|
||||
// page. Each company configures settings independently. Values are
|
||||
// persisted as CompanySetting keys: module.{slug}.{field_key}.
|
||||
//
|
||||
// Supported field types: text, textarea, select, switch, number
|
||||
// Labels use Laravel translation keys, resolved server-side.
|
||||
ModuleRegistry::registerSettings($slug, [
|
||||
'sections' => [
|
||||
[
|
||||
'title' => $this->nameLower.'::settings.greeting_section',
|
||||
'fields' => [
|
||||
[
|
||||
'key' => 'greeting',
|
||||
'type' => 'text',
|
||||
'label' => $this->nameLower.'::settings.greeting',
|
||||
'rules' => ['required', 'max:120'],
|
||||
'default' => 'Hello, world!',
|
||||
],
|
||||
[
|
||||
'key' => 'recipient',
|
||||
'type' => 'text',
|
||||
'label' => $this->nameLower.'::settings.recipient',
|
||||
'rules' => ['max:60'],
|
||||
'default' => 'friend',
|
||||
],
|
||||
[
|
||||
'key' => 'show_emoji',
|
||||
'type' => 'switch',
|
||||
'label' => $this->nameLower.'::settings.show_emoji',
|
||||
'default' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => $this->nameLower.'::settings.style_section',
|
||||
'fields' => [
|
||||
[
|
||||
'key' => 'tone',
|
||||
'type' => 'select',
|
||||
'label' => $this->nameLower.'::settings.tone',
|
||||
'rules' => ['required'],
|
||||
'default' => 'friendly',
|
||||
'options' => [
|
||||
'friendly' => 'Friendly',
|
||||
'formal' => 'Formal',
|
||||
'enthusiastic' => 'Enthusiastic',
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'note',
|
||||
'type' => 'textarea',
|
||||
'label' => $this->nameLower.'::settings.note',
|
||||
'rules' => ['max:500'],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
50
Modules/HelloWorld/app/Providers/RouteServiceProvider.php
Normal file
50
Modules/HelloWorld/app/Providers/RouteServiceProvider.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\HelloWorld\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
protected string $name = 'HelloWorld';
|
||||
|
||||
/**
|
||||
* Called before routes are registered.
|
||||
*
|
||||
* Register any model bindings or pattern based filters.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the routes for the application.
|
||||
*/
|
||||
public function map(): void
|
||||
{
|
||||
$this->mapApiRoutes();
|
||||
$this->mapWebRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the "web" routes for the application.
|
||||
*
|
||||
* These routes all receive session state, CSRF protection, etc.
|
||||
*/
|
||||
protected function mapWebRoutes(): void
|
||||
{
|
||||
Route::middleware('web')->group(module_path($this->name, '/routes/web.php'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the "api" routes for the application.
|
||||
*
|
||||
* These routes are typically stateless.
|
||||
*/
|
||||
protected function mapApiRoutes(): void
|
||||
{
|
||||
Route::middleware('api')->prefix('api')->name('api.')->group(module_path($this->name, '/routes/api.php'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user