phpvms/modules/Sample/Filament/Resources
Nabeel Shahzad a6a240bdfa
fix(addons): keep ModuleService working post-nwidart
- Add getStudlyName() to ModuleShim (fixes BadMethodCallException at
  ModuleSetupFilament:106 where $module->getStudlyName() is called)
- Remove Artisan::call('module:migrate', ...) from updateModule()
  (fixes CommandNotFoundException on admin enable — nwidart command
  gone; enable/disable lifecycle owned by setActive() + PrimeService)
- Remove unused Artisan import from ModuleService
- Add getStudlyName() assertion to ModuleShimTest
- Add ModuleServiceShimTest proving update/delete flows don't crash

feat(sample): add Filament resource + prove discovery

feat(addons): retire nwidart loader; wire engine

feat(addons): add Module facade shim

feat(addons): add base addon service provider

feat(addons): add FilamentPanelExtender

feat(addons): add runtime AddonLoader

feat(addons): boot-cache schema v2 + filament probe

feat(01-05): add phpvms:addons-prime console command

- Attribute-based: #[AsCommand] + #[Signature] (no $signature property)
- handle(PrimeService $prime): int — DI via handle()
- --force: calls prime->run() (always rebuilds); default: calls primeIfNeeded()
- components->task() for visual feedback, components->info() when cache present
- Returns self::SUCCESS / self::FAILURE with components->error() on exception
- AddonBootTest: command writes cache, Addon count>0, --force is idempotent

feat(01-05): add AddonServiceProvider and register in boot order

- AddonServiceProvider extends Illuminate\Support\ServiceProvider
- register() binds BootCache, ManifestParser, AutoloadGuard, AddonRegistry, PrimeService as singletons
- boot() skips guard+prime in console context (D-17); calls assertRuntimeAutoloadSupported() then primeIfNeeded() at HTTP/Octane boot (D-16, D-09)
- bootstrap/providers.php: AddonServiceProvider slots after ModulesServiceProvider, before AdminPanelProvider
- AddonBootTest: registration-order, provider-loaded, and console-skip (D-17) assertions

feat(01-04): implement PrimeService scan/upsert/cache

- run() scans base_path(modules) and storage_path(app/addons)
- firstOrNew+save upsert preserves operator enabled=false (D-12)
- enabled-only rows written to boot cache after each run (D-13)
- malformed manifests: Log::warning + skip, prime continues (D-15)
- T-04-03 path-traversal guard via realpath containment check
- primeIfNeeded() gates on BootCache::exists() (D-09/D-10)
- storage/app/addons absence treated as zero installed addons

test(01-04): add failing tests for PrimeService

- run() registers bundled modules, is idempotent (D-14)
- preserves operator enabled=false across re-prime (D-12)
- excludes disabled addons from boot cache (D-13)
- skips malformed manifests with Log warning (D-15)
- primeIfNeeded() gates on cache absence (D-09/D-10)
- handles absent storage/app/addons without error

feat(01-04): implement AddonRegistry read API

- enabled(): array → BootCache-sourced hot path (D-10)
- all(): Collection → Addon::query()->get() (no DB::table)
- find(): resolves by registry_id or path, returns null when absent
- Stateless, Octane-safe — no accumulated instance state
- 6 unit tests covering all three behaviors

feat(01-03): implement AutoloadGuard and AutoloadModeException

- AutoloadModeException extends RuntimeException with actionable message
  (names classmap-authoritative mode + composer dump-autoload fix)
- AutoloadGuard.isClassMapAuthoritative(?ClassLoader) checks authoritative mode
- AutoloadGuard.assertRuntimeAutoloadSupported() throws on authoritative loader
- No-arg path resolves via ClassLoader::getRegisteredLoaders() (prefers vendor key)
- Uses Composer\Autoload\ClassLoader — not App\Support\ClassLoader (naming collision avoided)

test(01-03): add failing test for AutoloadGuard

- authoritative loader returns true, non-authoritative returns false
- assertRuntimeAutoloadSupported throws AutoloadModeException for authoritative
- no-throw for non-authoritative loader
- AutoloadModeException message mentions classmap-authoritative and dump-autoload
- no-arg isClassMapAuthoritative() returns bool without error

feat(01-03): implement BootCache atomic boot-manifest read/write

- path() returns base_path('bootstrap/cache/addons.php')
- exists() reflects file presence
- read() requires the PHP cache; returns [] when absent
- write() serializes via var_export (no string interpolation, T-03-01)
- write() uses per-process temp file + rename() for POSIX atomicity (D-14)
- delete() removes file when present

test(01-03): add failing test for BootCache

- exists() returns false/true before/after write
- write() then read() round-trips addon array
- written PHP file passes php -l and require returns array
- no leftover temp files after write
- delete() removes file
- hostile registry_id round-trips via var_export escaping

feat(01-02): implement ManifestParser (lenient module.json + composer.json reader)

- parse() returns null on missing/malformed module.json (D-15, T-02-01)
- All nwidart keys optional with sane defaults (D-01)
- type defaults to 'module' when absent (D-02)
- blank/whitespace registry_id normalised to null (D-03, T-02-04)
- compat stored verbatim, no validation (D-05)
- namespace from autoload.psr-4 first key in composer.json, fallback to Modules\{Name} (D-07)
- version: module.json first, composer.json fallback, null if absent (D-07)
- Stateless class, no mutable instance properties (Octane-safe)
- No filesystem ops on manifest-supplied path/namespace/registryId (T-02-03)

test(01-02): add failing tests for ManifestParser

- Awards: legacy nwidart module, no phpVMS keys, no composer.json
- Sample: composer.json psr-4 dot key, version null
- VMSAcars: psr-4 empty-string key, version from composer.json
- phpVMS keys: type, compat, registry_id, version parsed verbatim
- Malformed JSON returns null, not exception (D-15)
- Missing module.json returns null
- Blank registry_id normalised to null (D-03)

feat(01-02): define ManifestData value-object contract

- Final readonly class in App\Addons namespace
- Constructor-promoted readonly properties: name, alias, type, compat,
  registryId, version, namespace, providers, path, raw
- PHPDoc array shapes for providers (list<string>) and raw (decoded JSON)
- Pure data contract, no Eloquent/Service dependencies
- Identity rules documented per D-03/D-04/MAN-03

feat(01-01): create App\Models\Addon Eloquent model with factory

- extends App\Contracts\Model (integer auto-increment id)
- table addons, fillable for all non-pk columns
- casts(): enabled => boolean, installed_at => datetime
- full $rules for all columns per Invite.php pattern
- full @property PHPDoc for D-06 columns
- HasFactory trait + AddonFactory with sensible defaults

test(01-01): add failing tests for App\Models\Addon

- asserts table name is addons
- asserts enabled cast to bool
- asserts installed_at cast to Carbon
- asserts nullable columns (registry_id, version, installed_at)
- asserts fillable for all non-pk columns

feat(01-01): create addons table migration with DDL and bundled data seed

- addons table with all D-06 columns (registry_id nullable unique, type,
  version, namespace, path, enabled, installed_at, timestamps)
- seeds Awards/Sample/VMSAcars bundled modules with NULL registry_id (D-08)
- namespace derived from composer.json psr-4 first key, fallback nwidart convention (D-07)
- version from module.json then composer.json then null (D-07)
- path sourced only from File::directories() output, not manifest (T-01-03)
- invalid JSON manifests skipped to avoid blocking migrations (D-15/T-01-01)
- idempotency guard: seeds only when table is empty

test(01-01): add failing tests for addons table migration

- asserts addons table with all D-06 columns is created
- asserts 3 bundled modules seeded with NULL registry_id
- asserts Awards namespace and null version
- asserts VMSAcars version 1.1.0
- asserts all bundled rows have type module, enabled true, path in modules/
- asserts down() drops the table
2026-06-05 12:25:15 -05:00
..
SampleResource/Pages fix(addons): keep ModuleService working post-nwidart 2026-06-05 12:25:15 -05:00
SampleResource.php fix(addons): keep ModuleService working post-nwidart 2026-06-05 12:25:15 -05:00