mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-07-16 22:05:20 +00:00
* 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.
123 lines
5.0 KiB
PHP
123 lines
5.0 KiB
PHP
<?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'],
|
|
],
|
|
],
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
}
|