From a00ca699b969b5ad852c5934389a108ad30cecba Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 18 Feb 2026 10:50:32 +0000 Subject: [PATCH 001/168] chore: update ArchiveVouchers date to 2024 --- app/Console/Commands/ArchiveVouchers.php | 14 +++++++++----- build/.keep | 0 2 files changed, 9 insertions(+), 5 deletions(-) delete mode 100644 build/.keep diff --git a/app/Console/Commands/ArchiveVouchers.php b/app/Console/Commands/ArchiveVouchers.php index 52c65d41a..476bfe401 100644 --- a/app/Console/Commands/ArchiveVouchers.php +++ b/app/Console/Commands/ArchiveVouchers.php @@ -13,7 +13,7 @@ class ArchiveVouchers extends Command { protected array $tableData = []; - protected string $date = "2023-09-01"; + protected string $date = "2024-09-01"; protected $signature = 'arc:archiveVouchers'; protected $description = 'archive vouchers and their histories'; @@ -120,8 +120,10 @@ private function populateTemporaryTable(): void private function getSelectionCriteria(): Builder { // Define and return the query for selecting vouchers to archive - return DB::table('vouchers')->whereIn('currentstate', - ['reimbursed', 'retired', 'voided', 'expired'])->where('updated_at', '<', $this->date); + return DB::table('vouchers')->whereIn( + 'currentstate', + ['reimbursed', 'retired', 'voided', 'expired'] + )->where('updated_at', '<', $this->date); } private function dropIndexes($tableName): void @@ -149,8 +151,10 @@ private function makeIndexes($tableName): void $keysFound = Arr::pluck(Schema::getForeignKeys($tableName), 'name'); foreach ($foreignKeys as $fk) { if (!array_key_exists($fk['name'], $keysFound)) { - $table->foreign($fk['columns'], - $fk['name'])->references($fk['foreign_columns'])->on($fk['foreign_table']); + $table->foreign( + $fk['columns'], + $fk['name'] + )->references($fk['foreign_columns'])->on($fk['foreign_table']); } } diff --git a/build/.keep b/build/.keep deleted file mode 100644 index e69de29bb..000000000 From aa7d908c17d890b6748c4d1dc5dd34d40d788c6f Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 17 Mar 2026 10:45:58 +0000 Subject: [PATCH 002/168] feature: purge family command --- app/Console/Commands/PurgeFamilyGraph.php | 90 +++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 app/Console/Commands/PurgeFamilyGraph.php diff --git a/app/Console/Commands/PurgeFamilyGraph.php b/app/Console/Commands/PurgeFamilyGraph.php new file mode 100644 index 000000000..f939f627b --- /dev/null +++ b/app/Console/Commands/PurgeFamilyGraph.php @@ -0,0 +1,90 @@ +argument('family_id'); + $dryRun = $this->option('dry-run'); + + $family = Family::find($familyId); + + if (!$family) { + $this->error("Family {$familyId} not found."); + return self::FAILURE; + } + + DB::beginTransaction(); + + try { + $registrations = Registration::where('family_id', $familyId)->pluck('id'); + + $bundles = Bundle::whereIn('registration_id', $registrations)->pluck('id'); + + $childrenCount = Child::where('family_id', $familyId)->count(); + $carersCount = Carer::withTrashed()->where('family_id', $familyId)->count(); + + $this->info("Family: {$familyId}"); + $this->line("Registrations: " . $registrations->count()); + $this->line("Bundles: " . $bundles->count()); + $this->line("Children: " . $childrenCount); + $this->line("Carers: " . $carersCount); + + if ($dryRun) { + DB::rollBack(); + $this->warn('Dry run complete — nothing deleted.'); + return self::SUCCESS; + } + + // 1️ Null vouchers.bundle_id + Voucher::whereIn('bundle_id', $bundles)->update([ + 'bundle_id' => null + ]); + + // 2️ Delete bundles + Bundle::whereIn('id', $bundles)->delete(); + + // 3️ Delete registrations + Registration::whereIn('id', $registrations)->delete(); + + // 4️ Delete children + Child::where('family_id', $familyId)->delete(); + + // 5️ Force delete carers (soft delete table) + Carer::withTrashed() + ->where('family_id', $familyId) + ->forceDelete(); + + // 6️ Delete family + $family->delete(); + + // commit the work + DB::commit(); + + $this->info('Family graph permanently deleted.'); + + return self::SUCCESS; + } catch (\Throwable $e) { + DB::rollBack(); + $this->error($e->getMessage()); + return self::FAILURE; + } + } +} From d1afbe23a6002f77286c856f1a4ffa476bc470f6 Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 17 Mar 2026 11:40:17 +0000 Subject: [PATCH 003/168] feature: purge family command improve --- app/Console/Commands/PurgeFamilyGraph.php | 198 +++++++++++++++------- 1 file changed, 140 insertions(+), 58 deletions(-) diff --git a/app/Console/Commands/PurgeFamilyGraph.php b/app/Console/Commands/PurgeFamilyGraph.php index f939f627b..531923e87 100644 --- a/app/Console/Commands/PurgeFamilyGraph.php +++ b/app/Console/Commands/PurgeFamilyGraph.php @@ -2,89 +2,171 @@ namespace App\Console\Commands; -use Illuminate\Console\Command; -use Illuminate\Support\Facades\DB; -use App\Family; -use App\Registration; use App\Bundle; use App\Carer; use App\Child; +use App\Family; +use App\Registration; use App\Voucher; +use Illuminate\Console\Command; +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; +use RuntimeException; +use Throwable; class PurgeFamilyGraph extends Command { - protected $signature = 'arc:purge-family + protected $signature = 'arc:purge-family {family_id : The family ID to purge} - {--dry-run : Show what would be deleted}'; + {--dry-run : Show what would be deleted} + {--force : Skip confirmation prompt}'; - protected $description = 'Permanently deletes a family and all related graph data from a centre, including voucher handouts'; + protected $description = 'Permanently deletes a family and related graph data, including voucher handouts'; public function handle(): int { $familyId = (int) $this->argument('family_id'); - $dryRun = $this->option('dry-run'); + $dryRun = (bool) $this->option('dry-run'); + $force = (bool) $this->option('force'); - $family = Family::find($familyId); - - if (!$family) { - $this->error("Family {$familyId} not found."); + try { + return DB::transaction(callback: function () use ($familyId, $dryRun, $force) { + + $family = Family::whereKey($familyId)->lockForUpdate()->first(); + + if (!$family) { + $this->error("Family {$familyId} not found."); + return self::FAILURE; + } + + $registrationIds = Registration::where('family_id', $familyId)->pluck('id'); + + $bundleIds = $registrationIds->isEmpty() + ? collect() + : Bundle::whereIn('registration_id', $registrationIds)->pluck('id'); + + $voucherCount = $bundleIds->isEmpty() + ? 0 + : Voucher::whereIn('bundle_id', $bundleIds)->count(); + + $childrenCount = Child::where('family_id', $familyId)->count(); + $carersCount = Carer::where('family_id', $familyId)->withTrashed()->count(); + + $this->info("Family: {$familyId}"); + $this->line("Registrations: {$registrationIds->count()}"); + $this->line("Bundles: {$bundleIds->count()}"); + $this->line("Vouchers to detach: {$voucherCount}"); + $this->line("Children: {$childrenCount}"); + $this->line("Carers (including trashed): {$carersCount}"); + + if ($dryRun) { + $this->warn('Dry run complete — nothing deleted.'); + return self::SUCCESS; + } + + if (!$force) { + $confirmed = $this->confirm( + "This will permanently purge family {$familyId} and related data. Continue?" + ); + + if (!$confirmed) { + $this->warn('Aborted.'); + return self::FAILURE; + } + } + + if ($bundleIds->isNotEmpty()) { + $this->detachVouchersFromBundles($bundleIds); + $this->deleteBundles($bundleIds); + } + + if ($registrationIds->isNotEmpty()) { + $this->deleteRegistrations($registrationIds); + } + + $deletedChildren = $this->deleteChildren($familyId); + if ($deletedChildren !== $childrenCount) { + throw new RuntimeException( + "Child delete mismatch. Expected {$childrenCount}, deleted {$deletedChildren}." + ); + } + + $deletedCarers = $this->deleteCarers($familyId); + if ($deletedCarers !== $carersCount) { + throw new RuntimeException( + "Carer delete mismatch. Expected {$carersCount}, deleted {$deletedCarers}." + ); + } + + $deletedFamily = $this->deleteFamily($family); + if ($deletedFamily !== 1) { + throw new RuntimeException("Family delete failed for family {$familyId}."); + } + + $this->info('Family graph permanently deleted.'); + return self::SUCCESS; + }, attempts: 3); + } catch (Throwable $e) { + report($e); + $this->error($e->getMessage()); return self::FAILURE; } + } - DB::beginTransaction(); - - try { - $registrations = Registration::where('family_id', $familyId)->pluck('id'); - - $bundles = Bundle::whereIn('registration_id', $registrations)->pluck('id'); - - $childrenCount = Child::where('family_id', $familyId)->count(); - $carersCount = Carer::withTrashed()->where('family_id', $familyId)->count(); - - $this->info("Family: {$familyId}"); - $this->line("Registrations: " . $registrations->count()); - $this->line("Bundles: " . $bundles->count()); - $this->line("Children: " . $childrenCount); - $this->line("Carers: " . $carersCount); - - if ($dryRun) { - DB::rollBack(); - $this->warn('Dry run complete — nothing deleted.'); - return self::SUCCESS; - } + private function detachVouchersFromBundles(Collection $bundleIds): void + { + foreach ($bundleIds->chunk(1000) as $chunk) { + Voucher::whereIn('bundle_id', $chunk->all())->update(['bundle_id' => null]); + } + } - // 1️ Null vouchers.bundle_id - Voucher::whereIn('bundle_id', $bundles)->update([ - 'bundle_id' => null - ]); + private function deleteRegistrations(Collection $registrationIds): void + { + $expected = $registrationIds->count(); + $deleted = 0; - // 2️ Delete bundles - Bundle::whereIn('id', $bundles)->delete(); + foreach ($registrationIds->chunk(1000) as $chunk) { + $affected = Registration::whereIn('id', $chunk->all())->delete(); + $deleted += $affected; + } - // 3️ Delete registrations - Registration::whereIn('id', $registrations)->delete(); + if ($deleted !== $expected) { + throw new RuntimeException( + "Registration delete mismatch. Expected {$expected}, deleted {$deleted}." + ); + } + } - // 4️ Delete children - Child::where('family_id', $familyId)->delete(); + private function deleteBundles(Collection $bundleIds): void + { + $expected = $bundleIds->count(); + $deleted = 0; - // 5️ Force delete carers (soft delete table) - Carer::withTrashed() - ->where('family_id', $familyId) - ->forceDelete(); + foreach ($bundleIds->chunk(1000) as $chunk) { + $affected = Bundle::whereIn('id', $chunk->all())->delete(); + $deleted += $affected; + } - // 6️ Delete family - $family->delete(); + if ($deleted !== $expected) { + throw new RuntimeException( + "Bundle delete mismatch. Expected {$expected}, deleted {$deleted}." + ); + } + } - // commit the work - DB::commit(); + private function deleteChildren(int $familyId): int + { + return (int) Child::where('family_id', $familyId)->delete(); + } - $this->info('Family graph permanently deleted.'); + private function deleteCarers(int $familyId): int + { + // carers are softDelete-able + return (int) Carer::where('family_id', $familyId)->withTrashed()->forceDelete(); + } - return self::SUCCESS; - } catch (\Throwable $e) { - DB::rollBack(); - $this->error($e->getMessage()); - return self::FAILURE; - } + private function deleteFamily($family): int + { + return (int) $family->delete(); } } From 7910dd5869d76daab9a3c03b9e32786e1c729248 Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 17 Mar 2026 14:36:23 +0000 Subject: [PATCH 004/168] feature: purge family command improve, again --- app/Console/Commands/PurgeFamilyGraph.php | 181 ++++++++++++++++++---- 1 file changed, 153 insertions(+), 28 deletions(-) diff --git a/app/Console/Commands/PurgeFamilyGraph.php b/app/Console/Commands/PurgeFamilyGraph.php index 531923e87..3153913a9 100644 --- a/app/Console/Commands/PurgeFamilyGraph.php +++ b/app/Console/Commands/PurgeFamilyGraph.php @@ -4,6 +4,7 @@ use App\Bundle; use App\Carer; +use App\Centre; use App\Child; use App\Family; use App\Registration; @@ -17,22 +18,147 @@ class PurgeFamilyGraph extends Command { protected $signature = 'arc:purge-family - {family_id : The family ID to purge} - {--dry-run : Show what would be deleted} - {--force : Skip confirmation prompt}'; + {family_id? : Single family ID to purge} + {--csv= : Path to CSV file containing an RVID column} + {--dry-run : Show what would be deleted} + {--force : Skip confirmation prompt}'; protected $description = 'Permanently deletes a family and related graph data, including voucher handouts'; public function handle(): int { - $familyId = (int) $this->argument('family_id'); - $dryRun = (bool) $this->option('dry-run'); - $force = (bool) $this->option('force'); + $familyId = $this->argument('family_id'); + $csvPath = $this->option('csv'); + $dryRun = (bool)$this->option('dry-run'); + $force = (bool)$this->option('force'); + if (!$familyId && !$csvPath) { + $this->error('Provide either a family_id OR --csv=path'); + return self::FAILURE; + } + + $familyIds = collect(); + + if ($familyId) { + $familyIds->push((int)$familyId); + } + + if ($csvPath) { + if (!file_exists($csvPath)) { + $this->error("CSV file not found: {$csvPath}"); + return self::FAILURE; + } + + $familyIds = $familyIds->merge( + $this->extractFamilyIdsFromCsv($csvPath) + ); + } + + $familyIds = $familyIds->filter()->unique()->values(); + + $this->info("Processing {$familyIds->count()} families"); + + $failed = []; + + foreach ($familyIds as $id) { + $this->line(''); + $this->line("==== FAMILY {$id} ===="); + + $result = $this->purgeSingleFamily($id, $dryRun, $force); + + if ($result !== self::SUCCESS) { + $failed[] = $id; + } + } + + $this->line(''); + $this->info('Batch complete.'); + + if (!empty($failed)) { + $this->warn('Failed family IDs:'); + $this->line(implode(', ', $failed)); + return self::FAILURE; + } + + return self::SUCCESS; + } + + private function extractFamilyIdsFromCsv(string $path): Collection + { + $ids = collect(); + + if (($handle = fopen($path, 'rb')) === false) { + throw new RuntimeException("Cannot open CSV: {$path}"); + } + + $header = fgetcsv($handle); + + if (!$header) { + fclose($handle); + throw new RuntimeException('CSV has no rows.'); + } + + $rvid = array_search('RVID', $header, true); + + if ($rvid === false) { + fclose($handle); + throw new RuntimeException('CSV missing RVID header column.'); + } + + while (($row = fgetcsv($handle)) !== false) { + $family = self::findByRvid($row[$rvid]); + if ($family !== null) { + $ids->push($family->id); + } else { + $this->line("Invalid rvid: {$rvid}"); + } + } + + fclose($handle); + return $ids; + } + + public static function findByRvid(string $rvid): ?Family + { + $rvid = strtoupper(trim($rvid)); + + if ($rvid === '') { + return null; + } + + // IMPORTANT: longest prefix first (prevents AB matching before AB1) + $centres = Centre::query() + ->select('id', 'prefix') + ->orderByRaw('LENGTH(prefix) DESC') + ->get()->all(); + + foreach ($centres as $centre) { + if (!str_starts_with($rvid, $centre->prefix)) { + continue; + } + $sequencePart = substr($rvid, strlen($centre->prefix)); + + if (!ctype_digit($sequencePart)) { + continue; + } + + $sequence = (int)$sequencePart; + + return Family::query() + ->where('initial_centre_id', $centre->id) + ->where('centre_sequence', $sequence) + ->first(); + } + + return null; + } + + private function purgeSingleFamily(int $familyId, bool $dryRun, bool $force): int + { try { return DB::transaction(callback: function () use ($familyId, $dryRun, $force) { - $family = Family::whereKey($familyId)->lockForUpdate()->first(); + $family = Family::whereKey($familyId)->lockForUpdate()->withPrimaryCarer()->first(); if (!$family) { $this->error("Family {$familyId} not found."); @@ -53,6 +179,7 @@ public function handle(): int $carersCount = Carer::where('family_id', $familyId)->withTrashed()->count(); $this->info("Family: {$familyId}"); + $this->info("Primary Carer: {$family->pri_carer}"); $this->line("Registrations: {$registrationIds->count()}"); $this->line("Bundles: {$bundleIds->count()}"); $this->line("Vouchers to detach: {$voucherCount}"); @@ -64,15 +191,13 @@ public function handle(): int return self::SUCCESS; } - if (!$force) { - $confirmed = $this->confirm( + if ( + !$force && !$this->confirm( "This will permanently purge family {$familyId} and related data. Continue?" - ); - - if (!$confirmed) { - $this->warn('Aborted.'); - return self::FAILURE; - } + ) + ) { + $this->warn('Aborted.'); + return self::FAILURE; } if ($bundleIds->isNotEmpty()) { @@ -120,53 +245,53 @@ private function detachVouchersFromBundles(Collection $bundleIds): void } } - private function deleteRegistrations(Collection $registrationIds): void + private function deleteBundles(Collection $bundleIds): void { - $expected = $registrationIds->count(); + $expected = $bundleIds->count(); $deleted = 0; - foreach ($registrationIds->chunk(1000) as $chunk) { - $affected = Registration::whereIn('id', $chunk->all())->delete(); + foreach ($bundleIds->chunk(1000) as $chunk) { + $affected = Bundle::whereIn('id', $chunk->all())->delete(); $deleted += $affected; } if ($deleted !== $expected) { throw new RuntimeException( - "Registration delete mismatch. Expected {$expected}, deleted {$deleted}." + "Bundle delete mismatch. Expected {$expected}, deleted {$deleted}." ); } } - private function deleteBundles(Collection $bundleIds): void + private function deleteRegistrations(Collection $registrationIds): void { - $expected = $bundleIds->count(); + $expected = $registrationIds->count(); $deleted = 0; - foreach ($bundleIds->chunk(1000) as $chunk) { - $affected = Bundle::whereIn('id', $chunk->all())->delete(); + foreach ($registrationIds->chunk(1000) as $chunk) { + $affected = Registration::whereIn('id', $chunk->all())->delete(); $deleted += $affected; } if ($deleted !== $expected) { throw new RuntimeException( - "Bundle delete mismatch. Expected {$expected}, deleted {$deleted}." + "Registration delete mismatch. Expected {$expected}, deleted {$deleted}." ); } } private function deleteChildren(int $familyId): int { - return (int) Child::where('family_id', $familyId)->delete(); + return (int)Child::where('family_id', $familyId)->delete(); } private function deleteCarers(int $familyId): int { // carers are softDelete-able - return (int) Carer::where('family_id', $familyId)->withTrashed()->forceDelete(); + return (int)Carer::where('family_id', $familyId)->withTrashed()->forceDelete(); } private function deleteFamily($family): int { - return (int) $family->delete(); + return (int)$family->delete(); } } From 1febbbbb03e88f5ecccd79fca5d00cfbf79ed740 Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 17 Mar 2026 15:17:25 +0000 Subject: [PATCH 005/168] feature: purge family command improve, again, again --- app/Console/Commands/PurgeFamilyGraph.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Console/Commands/PurgeFamilyGraph.php b/app/Console/Commands/PurgeFamilyGraph.php index 3153913a9..457bbd7eb 100644 --- a/app/Console/Commands/PurgeFamilyGraph.php +++ b/app/Console/Commands/PurgeFamilyGraph.php @@ -110,7 +110,7 @@ private function extractFamilyIdsFromCsv(string $path): Collection if ($family !== null) { $ids->push($family->id); } else { - $this->line("Invalid rvid: {$rvid}"); + $this->line("Invalid rvid: {$row[$rvid]}"); } } @@ -158,7 +158,7 @@ private function purgeSingleFamily(int $familyId, bool $dryRun, bool $force): in try { return DB::transaction(callback: function () use ($familyId, $dryRun, $force) { - $family = Family::whereKey($familyId)->lockForUpdate()->withPrimaryCarer()->first(); + $family = Family::withPrimaryCarer()->whereKey($familyId)->lockForUpdate()->first(); if (!$family) { $this->error("Family {$familyId} not found."); @@ -196,7 +196,7 @@ private function purgeSingleFamily(int $familyId, bool $dryRun, bool $force): in "This will permanently purge family {$familyId} and related data. Continue?" ) ) { - $this->warn('Aborted.'); + $this->warn('Skipped'); return self::FAILURE; } From ec30549f13e809a560cc8b924ed9f325dda7e31b Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 17 Mar 2026 16:24:10 +0000 Subject: [PATCH 006/168] feature: move family command --- .../Commands/MoveFamilyRegistrationCentre.php | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 app/Console/Commands/MoveFamilyRegistrationCentre.php diff --git a/app/Console/Commands/MoveFamilyRegistrationCentre.php b/app/Console/Commands/MoveFamilyRegistrationCentre.php new file mode 100644 index 000000000..05716f3d6 --- /dev/null +++ b/app/Console/Commands/MoveFamilyRegistrationCentre.php @@ -0,0 +1,238 @@ +argument('family_id'); + $centreId = $this->argument('centre_id'); + $csvPath = $this->option('csv'); + $dryRun = (bool)$this->option('dry-run'); + $force = (bool)$this->option('force'); + + if ( + ($csvPath && ($centreId || $familyId)) || + (!$csvPath && !($centreId && $familyId))) { + $this->error('Provide either a family_id and centre_id OR --csv=path'); + return self::FAILURE; + } + + $workList = collect(); + + if ($familyId && $centreId) { + $workList->push(["familyId" => (int)$familyId,"centreId" => (int)$centreId]); + } + + if ($csvPath) { + if (!file_exists($csvPath)) { + $this->error("CSV file not found: {$csvPath}"); + return self::FAILURE; + } + + $workList = $workList->merge( + $this->extractFromCsv($csvPath) + ); + } + + // can filter work on arrays? + $workList = $workList->filter()->unique( + function ($item) { + return $item['familyId'] . '-' . $item['centreId']; + } + )->values(); + + $this->info("Processing {$workList->count()} families"); + + $failed = []; + + foreach ($workList as $item) { + [$id, $centreId] = $item; + $this->line(''); + $this->line("==== FAMILY {$id} ===="); + + $result = $this->moveSingleFamily($id, $centreId, $dryRun, $force); + + if ($result !== self::SUCCESS) { + $failed[] = $id; + } + } + + $this->line(''); + $this->info('Batch complete.'); + + if (!empty($failed)) { + $this->warn('Failed family IDs:'); + $this->line(implode(', ', $failed)); + return self::FAILURE; + } + + return self::SUCCESS; + } + + private function extractFromCsv(string $path): Collection + { + $ids = collect(); + + if (($handle = fopen($path, 'rb')) === false) { + throw new RuntimeException("Cannot open CSV: {$path}"); + } + + $header = fgetcsv($handle); + + if (!$header) { + fclose($handle); + throw new RuntimeException('CSV has no rows.'); + } + + $rvid = array_search('RVID', $header, true); + + if ($rvid === false) { + fclose($handle); + throw new RuntimeException('CSV missing RVID header column.'); + } + + while (($row = fgetcsv($handle)) !== false) { + $family = self::findByRvid($row[$rvid]); + if ($family !== null) { + $ids->push($family->id); + } else { + $this->line("Invalid rvid: {$row[$rvid]}"); + } + } + + fclose($handle); + return $ids; + } + + public static function findByRvid(string $rvid): ?Family + { + $rvid = strtoupper(trim($rvid)); + + if ($rvid === '') { + return null; + } + + // IMPORTANT: longest prefix first (prevents AB matching before AB1) + $centres = Centre::query() + ->select('id', 'prefix') + ->orderByRaw('LENGTH(prefix) DESC') + ->get()->all(); + + foreach ($centres as $centre) { + if (!str_starts_with($rvid, $centre->prefix)) { + continue; + } + $sequencePart = substr($rvid, strlen($centre->prefix)); + + if (!ctype_digit($sequencePart)) { + continue; + } + + $sequence = (int)$sequencePart; + + return Family::query() + ->where('initial_centre_id', $centre->id) + ->where('centre_sequence', $sequence) + ->first(); + } + + return null; + } + + private function moveSingleFamily(int $familyId, int $centreId, bool $dryRun, bool $force): int + { + $centre = Centre::find($centreId); + if (!$centre) { + $this->error("Centre {$centreId} not found."); + return self::FAILURE; + } + + $family = Family::withPrimaryCarer()->whereKey($familyId)->lockForUpdate()->first(); + if (!$family) { + $this->error("Family {$familyId} not found."); + return self::FAILURE; + } + + try { + return DB::transaction(callback: function () use ($family, $centre, $dryRun, $force) { + + $registrations = Registration::where('family_id', $family->id)->with('centre')->get(); + $centreNames = $registrations->pluck('centre.name')->all(); + $centreNames = implode(", ", array_unique(array_sort($centreNames))); + + $this->info("Family: {$family->id}"); + $this->info("Primary Carer: {$family->pri_carer}"); + $this->line("Registrations: {$registrations->count()}"); + $this->line("In Centres: {$centreNames}"); + $this->line("Move to Centre: {$centre->id} ({$centre->name})"); + + if ($dryRun) { + $this->warn('Dry run complete — nothing deleted.'); + return self::SUCCESS; + } + + if ( + !$force && !$this->confirm( + "This will permanently move family {$family->id} and related data to {$centre->name}. Continue?" + ) + ) { + $this->warn('Skipped'); + return self::FAILURE; + } + + if ($registrations->isNotEmpty()) { + $this->moveRegistrations($registrations, $centre); + $family->lockToCentre($centre, true); + } + + $this->info('Family Registration permanently moved.'); + return self::SUCCESS; + }, attempts: 3); + } catch (Throwable $e) { + report($e); + $this->error($e->getMessage()); + return self::FAILURE; + } + } + + private function moveRegistrations(Collection $registrations, $centre): void + { + $expected = $registrations->count(); + $actioned = Registration::query() + ->whereIn('id', $registrations->modelKeys()) + ->update([ + 'centre_id' => $centre->id, + ]); + + if ($actioned !== $expected) { + throw new RuntimeException( + "Registration move mismatch. Expected {$expected}, moved {$actioned}." + ); + } + } +} From b4187bee335762e2cd1e762d8a4e5b716190152e Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 17 Mar 2026 16:47:30 +0000 Subject: [PATCH 007/168] feature: move family command, improved --- .../Commands/MoveFamilyRegistrationCentre.php | 57 ++++++++++++------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/app/Console/Commands/MoveFamilyRegistrationCentre.php b/app/Console/Commands/MoveFamilyRegistrationCentre.php index 05716f3d6..a57d3bfc0 100644 --- a/app/Console/Commands/MoveFamilyRegistrationCentre.php +++ b/app/Console/Commands/MoveFamilyRegistrationCentre.php @@ -2,15 +2,10 @@ namespace App\Console\Commands; -use App\Bundle; -use App\Carer; use App\Centre; -use App\Child; use App\Family; use App\Registration; -use App\Voucher; use Illuminate\Console\Command; -use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use RuntimeException; @@ -20,7 +15,7 @@ class MoveFamilyRegistrationCentre extends Command { protected $signature = 'arc:move-family-reg {family_id? : Single family ID to move} - {center_id? : to which centre} + {centre_id? : to which centre} {--csv= : Path to CSV file containing an RVID and CENTRE column} {--dry-run : Show what would be deleted} {--force : Skip confirmation prompt}'; @@ -45,7 +40,7 @@ public function handle(): int $workList = collect(); if ($familyId && $centreId) { - $workList->push(["familyId" => (int)$familyId,"centreId" => (int)$centreId]); + $workList->push(["familyId" => (int)$familyId, "centreId" => (int)$centreId]); } if ($csvPath) { @@ -71,14 +66,14 @@ function ($item) { $failed = []; foreach ($workList as $item) { - [$id, $centreId] = $item; + ['familyId' => $familyId, 'centreId' => $centreId] = $item; $this->line(''); - $this->line("==== FAMILY {$id} ===="); + $this->line("==== FAMILY {$familyId} ===="); - $result = $this->moveSingleFamily($id, $centreId, $dryRun, $force); + $result = $this->moveSingleFamily($familyId, $centreId, $dryRun, $force); if ($result !== self::SUCCESS) { - $failed[] = $id; + $failed[] = $familyId; } } @@ -96,7 +91,7 @@ function ($item) { private function extractFromCsv(string $path): Collection { - $ids = collect(); + $pairs = collect(); if (($handle = fopen($path, 'rb')) === false) { throw new RuntimeException("Cannot open CSV: {$path}"); @@ -109,24 +104,46 @@ private function extractFromCsv(string $path): Collection throw new RuntimeException('CSV has no rows.'); } - $rvid = array_search('RVID', $header, true); + $rvidIndex = array_search('RVID', $header, true); + $centreIndex = array_search('Centre', $header, true); - if ($rvid === false) { + if ($rvidIndex === false) { fclose($handle); throw new RuntimeException('CSV missing RVID header column.'); } + if ($centreIndex === false) { + fclose($handle); + throw new RuntimeException('CSV missing Centre header column.'); + } + + $centreMap = Centre::query()->pluck('id', 'name'); + while (($row = fgetcsv($handle)) !== false) { - $family = self::findByRvid($row[$rvid]); - if ($family !== null) { - $ids->push($family->id); - } else { - $this->line("Invalid rvid: {$row[$rvid]}"); + + $rvid = trim((string)($row[$rvidIndex] ?? '')); + $centreName = trim((string)($row[$centreIndex] ?? '')); + + $family = self::findByRvid($rvid); + + if (!$family) { + $this->line("Invalid RVID: {$rvid}"); + continue; } + + $centreId = $centreMap[$centreName] ?? null; + + if (!$centreId) { + $this->line("Invalid Centre: {$centreName}"); + continue; + } + + $pairs->push(['familyId' => $family->id, 'centreId' => $centreId]); } fclose($handle); - return $ids; + + return $pairs; } public static function findByRvid(string $rvid): ?Family From ade7bba35f412bb865c481cf7ce52bf45728e12d Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 17 Mar 2026 17:25:28 +0000 Subject: [PATCH 008/168] feature: move family command, improved, debugged --- .../Commands/MoveFamilyRegistrationCentre.php | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/MoveFamilyRegistrationCentre.php b/app/Console/Commands/MoveFamilyRegistrationCentre.php index a57d3bfc0..0c58c42a5 100644 --- a/app/Console/Commands/MoveFamilyRegistrationCentre.php +++ b/app/Console/Commands/MoveFamilyRegistrationCentre.php @@ -13,13 +13,13 @@ class MoveFamilyRegistrationCentre extends Command { + public array $summary = []; protected $signature = 'arc:move-family-reg {family_id? : Single family ID to move} {centre_id? : to which centre} {--csv= : Path to CSV file containing an RVID and CENTRE column} {--dry-run : Show what would be deleted} {--force : Skip confirmation prompt}'; - protected $description = 'Permanently moves a family and related graph data, including voucher handouts'; public function handle(): int @@ -86,6 +86,12 @@ function ($item) { return self::FAILURE; } + $path = "/tmp/movesDone.csv"; + if (count($this->summary) > 0) { + $this->writeCsv($path, $this->summary); + $this->line("Wrote changes to {$path}"); + } + return self::SUCCESS; } @@ -198,11 +204,14 @@ private function moveSingleFamily(int $familyId, int $centreId, bool $dryRun, bo try { return DB::transaction(callback: function () use ($family, $centre, $dryRun, $force) { + $oldRVID = $family->rvid; + $oldName = $family->pri_carer; + $registrations = Registration::where('family_id', $family->id)->with('centre')->get(); $centreNames = $registrations->pluck('centre.name')->all(); $centreNames = implode(", ", array_unique(array_sort($centreNames))); - $this->info("Family: {$family->id}"); + $this->info("Family: {$family->id} ({$family->rvid})"); $this->info("Primary Carer: {$family->pri_carer}"); $this->line("Registrations: {$registrations->count()}"); $this->line("In Centres: {$centreNames}"); @@ -225,8 +234,14 @@ private function moveSingleFamily(int $familyId, int $centreId, bool $dryRun, bo if ($registrations->isNotEmpty()) { $this->moveRegistrations($registrations, $centre); $family->lockToCentre($centre, true); + $family->save(); } + // refresh model. + $family->refresh(); + $newRVID = $family->rvid; + $this->summary[] = [$oldRVID, $oldName, $newRVID]; + $this->info('Family Registration permanently moved.'); return self::SUCCESS; }, attempts: 3); @@ -252,4 +267,16 @@ private function moveRegistrations(Collection $registrations, $centre): void ); } } + + private function writeCsv(string $path, iterable $rows): void + { + $handle = fopen($path, 'wb'); + if ($handle === false) { + throw new RuntimeException("Cannot write CSV to: {$path}"); + } + foreach ($rows as $row) { + fputcsv($handle, $row); + } + fclose($handle); + } } From b306497de415c0231f370cf153894d78c7c0e63a Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 17 Mar 2026 17:55:01 +0000 Subject: [PATCH 009/168] fix: typos --- app/Console/Commands/MoveFamilyRegistrationCentre.php | 2 +- app/Console/Commands/PurgeFamilyGraph.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/MoveFamilyRegistrationCentre.php b/app/Console/Commands/MoveFamilyRegistrationCentre.php index 0c58c42a5..f60d48ef5 100644 --- a/app/Console/Commands/MoveFamilyRegistrationCentre.php +++ b/app/Console/Commands/MoveFamilyRegistrationCentre.php @@ -17,7 +17,7 @@ class MoveFamilyRegistrationCentre extends Command protected $signature = 'arc:move-family-reg {family_id? : Single family ID to move} {centre_id? : to which centre} - {--csv= : Path to CSV file containing an RVID and CENTRE column} + {--csv= : Path to CSV file containing `RVID` and `Centre` columns} {--dry-run : Show what would be deleted} {--force : Skip confirmation prompt}'; protected $description = 'Permanently moves a family and related graph data, including voucher handouts'; diff --git a/app/Console/Commands/PurgeFamilyGraph.php b/app/Console/Commands/PurgeFamilyGraph.php index 457bbd7eb..68075bd84 100644 --- a/app/Console/Commands/PurgeFamilyGraph.php +++ b/app/Console/Commands/PurgeFamilyGraph.php @@ -19,7 +19,7 @@ class PurgeFamilyGraph extends Command { protected $signature = 'arc:purge-family {family_id? : Single family ID to purge} - {--csv= : Path to CSV file containing an RVID column} + {--csv= : Path to CSV file containing an `RVID` column} {--dry-run : Show what would be deleted} {--force : Skip confirmation prompt}'; From ac6220448b7cdafab764375dfa4624aacf34f995 Mon Sep 17 00:00:00 2001 From: charles strange Date: Fri, 20 Mar 2026 11:37:14 +0000 Subject: [PATCH 010/168] chore: update minor composer packages --- composer.json | 28 +- composer.lock | 1289 ++++++++++++++++++++++++++++--------------------- 2 files changed, 743 insertions(+), 574 deletions(-) diff --git a/composer.json b/composer.json index 6c559e260..d68c6b181 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "neontribe/arcvservice", "description": "ARCV Service", - "version": "1.19.0", + "version": "1.20.0", "keywords": [], "license": "proprietary", "type": "project", @@ -21,8 +21,8 @@ "ext-pdo": "*", "ext-sodium": "*", "ext-zip": "*", - "barryvdh/laravel-debugbar": "v3.16.1", - "barryvdh/laravel-dompdf": "v3.1.1", + "barryvdh/laravel-debugbar": "v3.16.5", + "barryvdh/laravel-dompdf": "v3.1.2", "fakerphp/faker": "1.24.1", "highsolutions/laravel-searchy": "12.0", "imtigger/laravel-job-status": "1.2.0", @@ -35,27 +35,27 @@ "maennchen/zipstream-php": "3.1.2", "neontribe/laravel-specification": "dev-master", "ramsey/uuid": "4.9.2", - "sebdesign/laravel-state-machine": "3.4.6", - "symfony/http-client": "v7.4.1", - "symfony/lock": "v7.4.1", - "symfony/mailchimp-mailer": "v7.4.0", + "sebdesign/laravel-state-machine": "v3.4.7", + "symfony/http-client": "v7.4.7", + "symfony/lock": "v7.4.6", + "symfony/mailchimp-mailer": "v7.4.6", "usmanhalalit/laracsv": "2.1.0", "werk365/etagconditionals": "dev-master", "ext-simplexml": "*" }, "require-dev": { - "barryvdh/laravel-ide-helper": "v3.6.1", - "friendsofphp/php-cs-fixer": "v3.92.0", - "larastan/larastan": "v3.8.1", + "barryvdh/laravel-ide-helper": "v3.7.0", + "friendsofphp/php-cs-fixer": "v3.94.2", + "larastan/larastan": "v3.9.3", "laravel/browser-kit-testing": "7.2.6", "laravel/dusk": "v8.3.4", "mockery/mockery": "1.6.12", "nunomaduro/collision": "v8.5.0", "phpmd/phpmd": "2.15.0", - "phpstan/phpstan": "2.1.33", - "phpunit/phpunit": "10.5.60", - "spatie/laravel-ignition": "2.9.1", - "squizlabs/php_codesniffer": "3.13.5" + "phpstan/phpstan": "2.1.42", + "phpunit/phpunit": "10.5.63", + "spatie/laravel-ignition": "2.12.0", + "squizlabs/php_codesniffer": "4.0.1" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index b6b58a1a9..de35272fe 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "123da6656440bd314ab249fc4e71b594", + "content-hash": "73e8c169f4f60fa35d79eadf6815070f", "packages": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.16.1", + "version": "v3.16.5", "source": { "type": "git", - "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "21b2c6fce05453efd4bceb34f9fddaa1cdb44090" + "url": "https://github.com/fruitcake/laravel-debugbar.git", + "reference": "e85c0a8464da67e5b4a53a42796d46a43fc06c9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/21b2c6fce05453efd4bceb34f9fddaa1cdb44090", - "reference": "21b2c6fce05453efd4bceb34f9fddaa1cdb44090", + "url": "https://api.github.com/repos/fruitcake/laravel-debugbar/zipball/e85c0a8464da67e5b4a53a42796d46a43fc06c9a", + "reference": "e85c0a8464da67e5b4a53a42796d46a43fc06c9a", "shasum": "" }, "require": { @@ -26,7 +26,7 @@ "illuminate/support": "^10|^11|^12", "php": "^8.1", "php-debugbar/php-debugbar": "^2.2.4", - "symfony/finder": "^6|^7" + "symfony/finder": "^6|^7|^8" }, "require-dev": { "mockery/mockery": "^1.3.3", @@ -76,8 +76,8 @@ "webprofiler" ], "support": { - "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.16.1" + "issues": "https://github.com/fruitcake/laravel-debugbar/issues", + "source": "https://github.com/fruitcake/laravel-debugbar/tree/v3.16.5" }, "funding": [ { @@ -89,30 +89,30 @@ "type": "github" } ], - "time": "2025-11-19T08:31:25+00:00" + "time": "2026-01-23T15:03:22+00:00" }, { "name": "barryvdh/laravel-dompdf", - "version": "v3.1.1", + "version": "v3.1.2", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-dompdf.git", - "reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d" + "reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d", - "reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d", + "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/ee3b72b19ccdf57d0243116ecb2b90261344dedc", + "reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc", "shasum": "" }, "require": { "dompdf/dompdf": "^3.0", - "illuminate/support": "^9|^10|^11|^12", + "illuminate/support": "^9|^10|^11|^12|^13.0", "php": "^8.1" }, "require-dev": { "larastan/larastan": "^2.7|^3.0", - "orchestra/testbench": "^7|^8|^9|^10", + "orchestra/testbench": "^7|^8|^9.16|^10|^11.0", "phpro/grumphp": "^2.5", "squizlabs/php_codesniffer": "^3.5" }, @@ -154,7 +154,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-dompdf/issues", - "source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.1" + "source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.2" }, "funding": [ { @@ -166,20 +166,20 @@ "type": "github" } ], - "time": "2025-02-13T15:07:54+00:00" + "time": "2026-02-21T08:51:10+00:00" }, { "name": "brick/math", - "version": "0.14.1", + "version": "0.14.8", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0" + "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0", - "reference": "f05858549e5f9d7bb45875a75583240a38a281d0", + "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629", + "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629", "shasum": "" }, "require": { @@ -218,7 +218,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.1" + "source": "https://github.com/brick/math/tree/0.14.8" }, "funding": [ { @@ -226,7 +226,7 @@ "type": "github" } ], - "time": "2025-11-24T14:40:29+00:00" + "time": "2026-02-10T14:33:43+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -608,16 +608,16 @@ }, { "name": "dompdf/dompdf", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "db712c90c5b9868df3600e64e68da62e78a34623" + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/db712c90c5b9868df3600e64e68da62e78a34623", - "reference": "db712c90c5b9868df3600e64e68da62e78a34623", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", "shasum": "" }, "require": { @@ -666,22 +666,22 @@ "homepage": "https://github.com/dompdf/dompdf", "support": { "issues": "https://github.com/dompdf/dompdf/issues", - "source": "https://github.com/dompdf/dompdf/tree/v3.1.4" + "source": "https://github.com/dompdf/dompdf/tree/v3.1.5" }, - "time": "2025-10-29T12:43:30+00:00" + "time": "2026-03-03T13:54:37+00:00" }, { "name": "dompdf/php-font-lib", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/dompdf/php-font-lib.git", - "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d" + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", - "reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a", "shasum": "" }, "require": { @@ -689,7 +689,7 @@ "php": "^7.1 || ^8.0" }, "require-dev": { - "symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6" + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12" }, "type": "library", "autoload": { @@ -711,31 +711,31 @@ "homepage": "https://github.com/dompdf/php-font-lib", "support": { "issues": "https://github.com/dompdf/php-font-lib/issues", - "source": "https://github.com/dompdf/php-font-lib/tree/1.0.1" + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.2" }, - "time": "2024-12-02T14:37:59+00:00" + "time": "2026-01-20T14:10:26+00:00" }, { "name": "dompdf/php-svg-lib", - "version": "1.0.0", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/dompdf/php-svg-lib.git", - "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af" + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/eb045e518185298eb6ff8d80d0d0c6b17aecd9af", - "reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^7.1 || ^8.0", - "sabberworm/php-css-parser": "^8.4" + "sabberworm/php-css-parser": "^8.4 || ^9.0" }, "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11" }, "type": "library", "autoload": { @@ -757,9 +757,9 @@ "homepage": "https://github.com/dompdf/php-svg-lib", "support": { "issues": "https://github.com/dompdf/php-svg-lib/issues", - "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.0" + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2" }, - "time": "2024-04-29T13:26:35+00:00" + "time": "2026-01-02T16:01:13+00:00" }, { "name": "dragonmantank/cron-expression", @@ -1091,24 +1091,24 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.1.3", + "version": "v1.1.4", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", - "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.3" + "phpoption/phpoption": "^1.9.5" }, "require-dev": { - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" }, "type": "library", "autoload": { @@ -1137,7 +1137,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" }, "funding": [ { @@ -1149,7 +1149,7 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:45:45+00:00" + "time": "2025-12-27T19:43:20+00:00" }, { "name": "guzzlehttp/guzzle", @@ -1362,16 +1362,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.8.0", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "21dc724a0583619cd1652f673303492272778051" + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", - "reference": "21dc724a0583619cd1652f673303492272778051", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", "shasum": "" }, "require": { @@ -1387,6 +1387,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "0.9.0", + "jshttp/mime-db": "1.54.0.1", "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { @@ -1458,7 +1459,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.8.0" + "source": "https://github.com/guzzle/psr7/tree/2.9.0" }, "funding": [ { @@ -1474,7 +1475,7 @@ "type": "tidelift" } ], - "time": "2025-08-23T21:21:41+00:00" + "time": "2026-03-10T16:41:02+00:00" }, { "name": "guzzlehttp/uri-template", @@ -2099,30 +2100,30 @@ }, { "name": "laravel/prompts", - "version": "v0.3.8", + "version": "v0.3.15", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "096748cdfb81988f60090bbb839ce3205ace0d35" + "reference": "4bb8107ec97651fd3f17f897d6489dbc4d8fb999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/096748cdfb81988f60090bbb839ce3205ace0d35", - "reference": "096748cdfb81988f60090bbb839ce3205ace0d35", + "url": "https://api.github.com/repos/laravel/prompts/zipball/4bb8107ec97651fd3f17f897d6489dbc4d8fb999", + "reference": "4bb8107ec97651fd3f17f897d6489dbc4d8fb999", "shasum": "" }, "require": { "composer-runtime-api": "^2.2", "ext-mbstring": "*", "php": "^8.1", - "symfony/console": "^6.2|^7.0" + "symfony/console": "^6.2|^7.0|^8.0" }, "conflict": { "illuminate/console": ">=10.17.0 <10.25.0", "laravel/framework": ">=10.17.0 <10.25.0" }, "require-dev": { - "illuminate/collections": "^10.0|^11.0|^12.0", + "illuminate/collections": "^10.0|^11.0|^12.0|^13.0", "mockery/mockery": "^1.5", "pestphp/pest": "^2.3|^3.4|^4.0", "phpstan/phpstan": "^1.12.28", @@ -2152,33 +2153,33 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.8" + "source": "https://github.com/laravel/prompts/tree/v0.3.15" }, - "time": "2025-11-21T20:52:52+00:00" + "time": "2026-03-17T13:45:17+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.7", + "version": "v2.0.10", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd" + "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/cb291e4c998ac50637c7eeb58189c14f5de5b9dd", - "reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669", + "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669", "shasum": "" }, "require": { "php": "^8.1" }, "require-dev": { - "illuminate/support": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", "nesbot/carbon": "^2.67|^3.0", "pestphp/pest": "^2.36|^3.0|^4.0", "phpstan/phpstan": "^2.0", - "symfony/var-dumper": "^6.2.0|^7.0.0" + "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0" }, "type": "library", "extra": { @@ -2215,7 +2216,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2025-11-21T20:52:36+00:00" + "time": "2026-02-20T19:59:49+00:00" }, { "name": "laravel/tinker", @@ -2485,16 +2486,16 @@ }, { "name": "league/commonmark", - "version": "2.8.0", + "version": "2.8.2", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb" + "reference": "59fb075d2101740c337c7216e3f32b36c204218b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb", - "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/59fb075d2101740c337c7216e3f32b36c204218b", + "reference": "59fb075d2101740c337c7216e3f32b36c204218b", "shasum": "" }, "require": { @@ -2519,9 +2520,9 @@ "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0 | ^7.0", - "symfony/process": "^5.4 | ^6.0 | ^7.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", + "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0", "unleashedtech/php-coding-standard": "^3.1.1", "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" }, @@ -2588,7 +2589,7 @@ "type": "tidelift" } ], - "time": "2025-11-26T21:48:24+00:00" + "time": "2026-03-19T13:16:38+00:00" }, { "name": "league/config", @@ -2674,16 +2675,16 @@ }, { "name": "league/csv", - "version": "9.27.1", + "version": "9.28.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797" + "reference": "6582ace29ae09ba5b07049d40ea13eb19c8b5073" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/26de738b8fccf785397d05ee2fc07b6cd8749797", - "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/6582ace29ae09ba5b07049d40ea13eb19c8b5073", + "reference": "6582ace29ae09ba5b07049d40ea13eb19c8b5073", "shasum": "" }, "require": { @@ -2693,14 +2694,14 @@ "require-dev": { "ext-dom": "*", "ext-xdebug": "*", - "friendsofphp/php-cs-fixer": "^3.75.0", - "phpbench/phpbench": "^1.4.1", - "phpstan/phpstan": "^1.12.27", + "friendsofphp/php-cs-fixer": "^3.92.3", + "phpbench/phpbench": "^1.4.3", + "phpstan/phpstan": "^1.12.32", "phpstan/phpstan-deprecation-rules": "^1.2.1", "phpstan/phpstan-phpunit": "^1.4.2", "phpstan/phpstan-strict-rules": "^1.6.2", - "phpunit/phpunit": "^10.5.16 || ^11.5.22 || ^12.3.6", - "symfony/var-dumper": "^6.4.8 || ^7.3.0" + "phpunit/phpunit": "^10.5.16 || ^11.5.22 || ^12.5.4", + "symfony/var-dumper": "^6.4.8 || ^7.4.0 || ^8.0" }, "suggest": { "ext-dom": "Required to use the XMLConverter and the HTMLConverter classes", @@ -2761,7 +2762,7 @@ "type": "github" } ], - "time": "2025-10-25T08:35:20+00:00" + "time": "2025-12-27T15:18:42+00:00" }, { "name": "league/event", @@ -2819,16 +2820,16 @@ }, { "name": "league/flysystem", - "version": "3.30.2", + "version": "3.32.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277" + "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", - "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/254b1595b16b22dbddaaef9ed6ca9fdac4956725", + "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725", "shasum": "" }, "require": { @@ -2896,22 +2897,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.30.2" + "source": "https://github.com/thephpleague/flysystem/tree/3.32.0" }, - "time": "2025-11-10T17:13:11+00:00" + "time": "2026-02-25T17:01:41+00:00" }, { "name": "league/flysystem-local", - "version": "3.30.2", + "version": "3.31.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d" + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ab4f9d0d672f601b102936aa728801dd1a11968d", - "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", "shasum": "" }, "require": { @@ -2945,9 +2946,9 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.2" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" }, - "time": "2025-11-10T11:23:37+00:00" + "time": "2026-01-23T15:30:45+00:00" }, { "name": "league/mime-type-detection", @@ -3095,20 +3096,20 @@ }, { "name": "league/uri", - "version": "7.7.0", + "version": "7.8.1", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807" + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/8d587cddee53490f9b82bf203d3a9aa7ea4f9807", - "reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/08cf38e3924d4f56238125547b5720496fac8fd4", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.7", + "league/uri-interfaces": "^7.8.1", "php": "^8.1", "psr/http-factory": "^1" }, @@ -3122,11 +3123,11 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "ext-uri": "to use the PHP native URI class", - "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", - "league/uri-components": "Needed to easily manipulate URI objects components", - "league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", "php-64bit": "to improve IPV4 host parsing", - "rowbot/url": "to handle WHATWG URL", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -3181,7 +3182,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.7.0" + "source": "https://github.com/thephpleague/uri/tree/7.8.1" }, "funding": [ { @@ -3189,20 +3190,20 @@ "type": "github" } ], - "time": "2025-12-07T16:02:06+00:00" + "time": "2026-03-15T20:22:25+00:00" }, { "name": "league/uri-interfaces", - "version": "7.7.0", + "version": "7.8.1", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c" + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/62ccc1a0435e1c54e10ee6022df28d6c04c2946c", - "reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/85d5c77c5d6d3af6c54db4a78246364908f3c928", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928", "shasum": "" }, "require": { @@ -3215,7 +3216,7 @@ "ext-gmp": "to improve IPV4 host parsing", "ext-intl": "to handle IDN host with the best performance", "php-64bit": "to improve IPV4 host parsing", - "rowbot/url": "to handle WHATWG URL", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" }, "type": "library", @@ -3265,7 +3266,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.7.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.1" }, "funding": [ { @@ -3273,7 +3274,7 @@ "type": "github" } ], - "time": "2025-12-07T16:03:21+00:00" + "time": "2026-03-08T20:05:35+00:00" }, { "name": "maennchen/zipstream-php", @@ -3422,16 +3423,16 @@ }, { "name": "monolog/monolog", - "version": "3.9.0", + "version": "3.10.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", - "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", "shasum": "" }, "require": { @@ -3449,7 +3450,7 @@ "graylog2/gelf-php": "^1.4.2 || ^2.0", "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/psr7": "^2.2", - "mongodb/mongodb": "^1.8", + "mongodb/mongodb": "^1.8 || ^2.0", "php-amqplib/php-amqplib": "~2.4 || ^3", "php-console/php-console": "^3.1.8", "phpstan/phpstan": "^2", @@ -3509,7 +3510,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.9.0" + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" }, "funding": [ { @@ -3521,7 +3522,7 @@ "type": "tidelift" } ], - "time": "2025-03-24T10:02:05+00:00" + "time": "2026-01-02T08:56:05+00:00" }, { "name": "neontribe/laravel-specification", @@ -3581,16 +3582,16 @@ }, { "name": "nesbot/carbon", - "version": "3.11.0", + "version": "3.11.3", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "bdb375400dcd162624531666db4799b36b64e4a1" + "reference": "6a7e652845bb018c668220c2a545aded8594fbbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/bdb375400dcd162624531666db4799b36b64e4a1", - "reference": "bdb375400dcd162624531666db4799b36b64e4a1", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6a7e652845bb018c668220c2a545aded8594fbbf", + "reference": "6a7e652845bb018c668220c2a545aded8594fbbf", "shasum": "" }, "require": { @@ -3614,7 +3615,7 @@ "phpstan/extension-installer": "^1.4.3", "phpstan/phpstan": "^2.1.22", "phpunit/phpunit": "^10.5.53", - "squizlabs/php_codesniffer": "^3.13.4" + "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" }, "bin": [ "bin/carbon" @@ -3657,14 +3658,14 @@ } ], "description": "An API extension for DateTime that supports 281 different languages.", - "homepage": "https://carbon.nesbot.com", + "homepage": "https://carbonphp.github.io/carbon/", "keywords": [ "date", "datetime", "time" ], "support": { - "docs": "https://carbon.nesbot.com/docs", + "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", "issues": "https://github.com/CarbonPHP/carbon/issues", "source": "https://github.com/CarbonPHP/carbon" }, @@ -3682,20 +3683,20 @@ "type": "tidelift" } ], - "time": "2025-12-02T21:04:28+00:00" + "time": "2026-03-11T17:23:39+00:00" }, { "name": "nette/schema", - "version": "v1.3.3", + "version": "v1.3.5", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004" + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004", - "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004", + "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", "shasum": "" }, "require": { @@ -3703,8 +3704,10 @@ "php": "8.1 - 8.5" }, "require-dev": { - "nette/tester": "^2.5.2", - "phpstan/phpstan-nette": "^2.0@stable", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.6", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.39@stable", "tracy/tracy": "^2.8" }, "type": "library", @@ -3745,22 +3748,22 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.3" + "source": "https://github.com/nette/schema/tree/v1.3.5" }, - "time": "2025-10-30T22:57:59+00:00" + "time": "2026-02-23T03:47:12+00:00" }, { "name": "nette/utils", - "version": "v4.1.0", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "fa1f0b8261ed150447979eb22e373b7b7ad5a8e0" + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/fa1f0b8261ed150447979eb22e373b7b7ad5a8e0", - "reference": "fa1f0b8261ed150447979eb22e373b7b7ad5a8e0", + "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", "shasum": "" }, "require": { @@ -3772,8 +3775,10 @@ }, "require-dev": { "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", "nette/tester": "^2.5", - "phpstan/phpstan-nette": "^2.0@stable", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", "tracy/tracy": "^2.9" }, "suggest": { @@ -3834,9 +3839,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.0" + "source": "https://github.com/nette/utils/tree/v4.1.3" }, - "time": "2025-12-01T17:49:23+00:00" + "time": "2026-02-13T03:05:33+00:00" }, { "name": "nikic/php-parser", @@ -3898,31 +3903,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.3.3", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017" + "reference": "712a31b768f5daea284c2169a7d227031001b9a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/6fb2a640ff502caace8e05fd7be3b503a7e1c017", - "reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.3.6" + "symfony/console": "^7.4.4 || ^8.0.4" }, "require-dev": { - "illuminate/console": "^11.46.1", - "laravel/pint": "^1.25.1", + "illuminate/console": "^11.47.0", + "laravel/pint": "^1.27.1", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.1.3", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2", "phpstan/phpstan": "^1.12.32", "phpstan/phpstan-strict-rules": "^1.6.2", - "symfony/var-dumper": "^7.3.5", + "symfony/var-dumper": "^7.3.5 || ^8.0.4", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -3954,7 +3959,7 @@ "email": "enunomaduro@gmail.com" } ], - "description": "Its like Tailwind CSS, but for the console.", + "description": "It's like Tailwind CSS, but for the console.", "keywords": [ "cli", "console", @@ -3965,7 +3970,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.3" + "source": "https://github.com/nunomaduro/termwind/tree/v2.4.0" }, "funding": [ { @@ -3981,7 +3986,7 @@ "type": "github" } ], - "time": "2025-11-20T02:34:59+00:00" + "time": "2026-02-16T23:10:27+00:00" }, { "name": "nyholm/psr7", @@ -4182,31 +4187,32 @@ }, { "name": "php-debugbar/php-debugbar", - "version": "v2.2.4", + "version": "v2.2.6", "source": { "type": "git", "url": "https://github.com/php-debugbar/php-debugbar.git", - "reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35" + "reference": "abb9fa3c5c8dbe7efe03ddba56782917481de3e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/3146d04671f51f69ffec2a4207ac3bdcf13a9f35", - "reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35", + "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/abb9fa3c5c8dbe7efe03ddba56782917481de3e8", + "reference": "abb9fa3c5c8dbe7efe03ddba56782917481de3e8", "shasum": "" }, "require": { - "php": "^8", + "php": "^8.1", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^4|^5|^6|^7" + "symfony/var-dumper": "^5.4|^6.4|^7.3|^8.0" }, "replace": { "maximebf/debugbar": "self.version" }, "require-dev": { "dbrekelmans/bdi": "^1", - "phpunit/phpunit": "^8|^9", + "phpunit/phpunit": "^10", + "symfony/browser-kit": "^6.0|7.0", "symfony/panther": "^1|^2.1", - "twig/twig": "^1.38|^2.7|^3.0" + "twig/twig": "^3.11.2" }, "suggest": { "kriswallsmith/assetic": "The best way to manage assets", @@ -4216,7 +4222,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.2-dev" } }, "autoload": { @@ -4249,22 +4255,22 @@ ], "support": { "issues": "https://github.com/php-debugbar/php-debugbar/issues", - "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.2.4" + "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.2.6" }, - "time": "2025-07-22T14:01:30+00:00" + "time": "2025-12-22T13:21:32+00:00" }, { "name": "phpoption/phpoption", - "version": "1.9.4", + "version": "1.9.5", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d" + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", - "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", "shasum": "" }, "require": { @@ -4314,7 +4320,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.4" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" }, "funding": [ { @@ -4326,20 +4332,20 @@ "type": "tidelift" } ], - "time": "2025-08-21T11:53:16+00:00" + "time": "2025-12-27T19:41:33+00:00" }, { "name": "phpseclib/phpseclib", - "version": "3.0.48", + "version": "3.0.50", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "64065a5679c50acb886e82c07aa139b0f757bb89" + "reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/64065a5679c50acb886e82c07aa139b0f757bb89", - "reference": "64065a5679c50acb886e82c07aa139b0f757bb89", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/aa6ad8321ed103dc3624fb600a25b66ebf78ec7b", + "reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b", "shasum": "" }, "require": { @@ -4420,7 +4426,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.48" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.50" }, "funding": [ { @@ -4436,7 +4442,7 @@ "type": "tidelift" } ], - "time": "2025-12-15T11:51:42+00:00" + "time": "2026-03-19T02:57:58+00:00" }, { "name": "psr/cache", @@ -4901,16 +4907,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.17", + "version": "v0.12.21", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "85fbbd9f3064e157fc21fe4362b2b5c19f2ea631" + "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/85fbbd9f3064e157fc21fe4362b2b5c19f2ea631", - "reference": "85fbbd9f3064e157fc21fe4362b2b5c19f2ea631", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", + "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", "shasum": "" }, "require": { @@ -4974,9 +4980,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.17" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.21" }, - "time": "2025-12-15T04:55:34+00:00" + "time": "2026-03-06T21:21:28+00:00" }, { "name": "ralouphie/getallheaders", @@ -5178,25 +5184,35 @@ }, { "name": "sabberworm/php-css-parser", - "version": "v8.9.0", + "version": "v9.3.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", - "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9" + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d8e916507b88e389e26d4ab03c904a082aa66bb9", - "reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", "shasum": "" }, "require": { "ext-iconv": "*", - "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" - }, - "require-dev": { - "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41", - "rawr/cross-data-providers": "^2.0.0" + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/extension-installer": "1.4.3", + "phpstan/phpstan": "1.12.32 || 2.1.32", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.8", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7", + "phpunit/phpunit": "8.5.52", + "rawr/phpunit-data-provider": "3.3.1", + "rector/rector": "1.2.10 || 2.2.8", + "rector/type-perfect": "1.0.0 || 2.1.0", + "squizlabs/php_codesniffer": "4.0.1", + "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" @@ -5204,10 +5220,14 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "9.0.x-dev" + "dev-main": "9.4.x-dev" } }, "autoload": { + "files": [ + "src/Rule/Rule.php", + "src/RuleSet/RuleContainer.php" + ], "psr-4": { "Sabberworm\\CSS\\": "src/" } @@ -5238,33 +5258,33 @@ ], "support": { "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", - "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.9.0" + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0" }, - "time": "2025-07-11T13:20:48+00:00" + "time": "2026-03-03T17:31:43+00:00" }, { "name": "sebdesign/laravel-state-machine", - "version": "v3.4.6", + "version": "v3.4.7", "source": { "type": "git", "url": "https://github.com/sebdesign/laravel-state-machine.git", - "reference": "3fc8bf3e9283a0c23582d10bafbf06b20100b119" + "reference": "8a02115687bd4c058503b67ebc278c94dec34130" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebdesign/laravel-state-machine/zipball/3fc8bf3e9283a0c23582d10bafbf06b20100b119", - "reference": "3fc8bf3e9283a0c23582d10bafbf06b20100b119", + "url": "https://api.github.com/repos/sebdesign/laravel-state-machine/zipball/8a02115687bd4c058503b67ebc278c94dec34130", + "reference": "8a02115687bd4c058503b67ebc278c94dec34130", "shasum": "" }, "require": { - "illuminate/support": "^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0", + "illuminate/support": "^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0 | ^13.0", "php": "^7.2.5 | ^8.0", "winzou/state-machine": "^0.4.2|^0.4" }, "require-dev": { "mockery/mockery": "^1.3.1", - "orchestra/testbench": "^5.0 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0", - "phpunit/phpunit": "^8.5 | ^9.3 | ^10.5 | ^11.5.3", + "orchestra/testbench": "^5.0 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0", + "phpunit/phpunit": "^8.5 | ^9.3 | ^10.5 | ^11.5.3 | ^12.5.12", "symfony/process": "^4.3 | ^5.0 | ^6.0 | ^7.0" }, "type": "library", @@ -5305,22 +5325,22 @@ ], "support": { "issues": "https://github.com/sebdesign/laravel-state-machine/issues", - "source": "https://github.com/sebdesign/laravel-state-machine/tree/v3.4.6" + "source": "https://github.com/sebdesign/laravel-state-machine/tree/v3.4.7" }, - "time": "2025-03-29T02:09:00+00:00" + "time": "2026-03-15T16:54:53+00:00" }, { "name": "symfony/cache", - "version": "v7.3.8", + "version": "v7.3.11", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "288ea9853bbf6b395ee09bde9ac6da415fffbc8c" + "reference": "e3e76b9ba0dff3dfe08ebda500723976dd9de407" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/288ea9853bbf6b395ee09bde9ac6da415fffbc8c", - "reference": "288ea9853bbf6b395ee09bde9ac6da415fffbc8c", + "url": "https://api.github.com/repos/symfony/cache/zipball/e3e76b9ba0dff3dfe08ebda500723976dd9de407", + "reference": "e3e76b9ba0dff3dfe08ebda500723976dd9de407", "shasum": "" }, "require": { @@ -5389,7 +5409,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.3.8" + "source": "https://github.com/symfony/cache/tree/v7.3.11" }, "funding": [ { @@ -5409,7 +5429,7 @@ "type": "tidelift" } ], - "time": "2025-12-04T18:07:52+00:00" + "time": "2026-01-27T16:12:03+00:00" }, { "name": "symfony/cache-contracts", @@ -5567,16 +5587,16 @@ }, { "name": "symfony/console", - "version": "v7.4.1", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e" + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e", - "reference": "6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e", + "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", "shasum": "" }, "require": { @@ -5641,7 +5661,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.1" + "source": "https://github.com/symfony/console/tree/v7.4.7" }, "funding": [ { @@ -5661,20 +5681,20 @@ "type": "tidelift" } ], - "time": "2025-12-05T15:23:39+00:00" + "time": "2026-03-06T14:06:20+00:00" }, { "name": "symfony/css-selector", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135" + "reference": "2e7c52c647b406e2107dd867db424a4dbac91864" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab862f478513e7ca2fe9ec117a6f01a8da6e1135", - "reference": "ab862f478513e7ca2fe9ec117a6f01a8da6e1135", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/2e7c52c647b406e2107dd867db424a4dbac91864", + "reference": "2e7c52c647b406e2107dd867db424a4dbac91864", "shasum": "" }, "require": { @@ -5710,7 +5730,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.4.0" + "source": "https://github.com/symfony/css-selector/tree/v7.4.6" }, "funding": [ { @@ -5730,7 +5750,7 @@ "type": "tidelift" } ], - "time": "2025-10-30T13:39:42+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5801,16 +5821,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2" + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/48be2b0653594eea32dcef130cca1c811dcf25c2", - "reference": "48be2b0653594eea32dcef130cca1c811dcf25c2", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8", "shasum": "" }, "require": { @@ -5859,7 +5879,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.4.0" + "source": "https://github.com/symfony/error-handler/tree/v7.4.4" }, "funding": [ { @@ -5879,20 +5899,20 @@ "type": "tidelift" } ], - "time": "2025-11-05T14:29:59+00:00" + "time": "2026-01-20T16:42:42+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d" + "reference": "dc2c0eba1af673e736bb851d747d266108aea746" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9dddcddff1ef974ad87b3708e4b442dc38b2261d", - "reference": "9dddcddff1ef974ad87b3708e4b442dc38b2261d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746", + "reference": "dc2c0eba1af673e736bb851d747d266108aea746", "shasum": "" }, "require": { @@ -5944,7 +5964,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.4" }, "funding": [ { @@ -5964,7 +5984,7 @@ "type": "tidelift" } ], - "time": "2025-10-28T09:38:46+00:00" + "time": "2026-01-05T11:45:34+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -6044,16 +6064,16 @@ }, { "name": "symfony/expression-language", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/expression-language.git", - "reference": "8b9bbbb8c71f79a09638f6ea77c531e511139efa" + "reference": "f3a6497eb6573e185f2ec41cd3b3f0cd68ddf667" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/8b9bbbb8c71f79a09638f6ea77c531e511139efa", - "reference": "8b9bbbb8c71f79a09638f6ea77c531e511139efa", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/f3a6497eb6573e185f2ec41cd3b3f0cd68ddf667", + "reference": "f3a6497eb6573e185f2ec41cd3b3f0cd68ddf667", "shasum": "" }, "require": { @@ -6088,7 +6108,7 @@ "description": "Provides an engine that can compile and evaluate expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/expression-language/tree/v7.4.0" + "source": "https://github.com/symfony/expression-language/tree/v7.4.4" }, "funding": [ { @@ -6108,20 +6128,20 @@ "type": "tidelift" } ], - "time": "2025-11-12T15:39:26+00:00" + "time": "2026-01-05T08:47:25+00:00" }, { "name": "symfony/finder", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "340b9ed7320570f319028a2cbec46d40535e94bd" + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/340b9ed7320570f319028a2cbec46d40535e94bd", - "reference": "340b9ed7320570f319028a2cbec46d40535e94bd", + "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf", "shasum": "" }, "require": { @@ -6156,7 +6176,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.0" + "source": "https://github.com/symfony/finder/tree/v7.4.6" }, "funding": [ { @@ -6176,20 +6196,20 @@ "type": "tidelift" } ], - "time": "2025-11-05T05:42:40+00:00" + "time": "2026-01-29T09:40:50+00:00" }, { "name": "symfony/http-client", - "version": "v7.4.1", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "26cc224ea7103dda90e9694d9e139a389092d007" + "reference": "1010624285470eb60e88ed10035102c75b4ea6af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/26cc224ea7103dda90e9694d9e139a389092d007", - "reference": "26cc224ea7103dda90e9694d9e139a389092d007", + "url": "https://api.github.com/repos/symfony/http-client/zipball/1010624285470eb60e88ed10035102c75b4ea6af", + "reference": "1010624285470eb60e88ed10035102c75b4ea6af", "shasum": "" }, "require": { @@ -6257,7 +6277,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.1" + "source": "https://github.com/symfony/http-client/tree/v7.4.7" }, "funding": [ { @@ -6277,7 +6297,7 @@ "type": "tidelift" } ], - "time": "2025-12-04T21:12:57+00:00" + "time": "2026-03-05T11:16:58+00:00" }, { "name": "symfony/http-client-contracts", @@ -6359,16 +6379,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.4.1", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "bd1af1e425811d6f077db240c3a588bdb405cd27" + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/bd1af1e425811d6f077db240c3a588bdb405cd27", - "reference": "bd1af1e425811d6f077db240c3a588bdb405cd27", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f94b3e7b7dafd40e666f0c9ff2084133bae41e81", + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81", "shasum": "" }, "require": { @@ -6417,7 +6437,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.1" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.7" }, "funding": [ { @@ -6437,20 +6457,20 @@ "type": "tidelift" } ], - "time": "2025-12-07T11:13:10+00:00" + "time": "2026-03-06T13:15:18+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.2", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "f6e6f0a5fa8763f75a504b930163785fb6dd055f" + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f6e6f0a5fa8763f75a504b930163785fb6dd055f", - "reference": "f6e6f0a5fa8763f75a504b930163785fb6dd055f", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3b3fcf386c809be990c922e10e4c620d6367cab1", + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1", "shasum": "" }, "require": { @@ -6492,7 +6512,7 @@ "symfony/config": "^6.4|^7.0|^8.0", "symfony/console": "^6.4|^7.0|^8.0", "symfony/css-selector": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", "symfony/dom-crawler": "^6.4|^7.0|^8.0", "symfony/expression-language": "^6.4|^7.0|^8.0", "symfony/finder": "^6.4|^7.0|^8.0", @@ -6536,7 +6556,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.2" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.7" }, "funding": [ { @@ -6556,20 +6576,20 @@ "type": "tidelift" } ], - "time": "2025-12-08T07:43:37+00:00" + "time": "2026-03-06T16:33:18+00:00" }, { "name": "symfony/lock", - "version": "v7.4.1", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/lock.git", - "reference": "8ba5de8ab1b2d1766b654f60f1551bf4aefc542f" + "reference": "c39d02f61a039ef660e44f36719b1440414fe493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/lock/zipball/8ba5de8ab1b2d1766b654f60f1551bf4aefc542f", - "reference": "8ba5de8ab1b2d1766b654f60f1551bf4aefc542f", + "url": "https://api.github.com/repos/symfony/lock/zipball/c39d02f61a039ef660e44f36719b1440414fe493", + "reference": "c39d02f61a039ef660e44f36719b1440414fe493", "shasum": "" }, "require": { @@ -6619,7 +6639,7 @@ "semaphore" ], "support": { - "source": "https://github.com/symfony/lock/tree/v7.4.1" + "source": "https://github.com/symfony/lock/tree/v7.4.6" }, "funding": [ { @@ -6639,20 +6659,20 @@ "type": "tidelift" } ], - "time": "2025-12-04T21:29:12+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "symfony/mailchimp-mailer", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/mailchimp-mailer.git", - "reference": "f78b85ee9413355f516928a0336230205e22fbef" + "reference": "d3d861e9ceeeadcd7b9874f41bd217e9537517a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailchimp-mailer/zipball/f78b85ee9413355f516928a0336230205e22fbef", - "reference": "f78b85ee9413355f516928a0336230205e22fbef", + "url": "https://api.github.com/repos/symfony/mailchimp-mailer/zipball/d3d861e9ceeeadcd7b9874f41bd217e9537517a6", + "reference": "d3d861e9ceeeadcd7b9874f41bd217e9537517a6", "shasum": "" }, "require": { @@ -6692,7 +6712,7 @@ "description": "Symfony Mailchimp Mailer Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailchimp-mailer/tree/v7.4.0" + "source": "https://github.com/symfony/mailchimp-mailer/tree/v7.4.6" }, "funding": [ { @@ -6712,20 +6732,20 @@ "type": "tidelift" } ], - "time": "2025-09-19T10:30:36+00:00" + "time": "2026-02-23T13:47:45+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd" + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", - "reference": "a3d9eea8cfa467ece41f0f54ba28185d74bd53fd", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9", "shasum": "" }, "require": { @@ -6776,7 +6796,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.0" + "source": "https://github.com/symfony/mailer/tree/v7.4.6" }, "funding": [ { @@ -6796,20 +6816,20 @@ "type": "tidelift" } ], - "time": "2025-11-21T15:26:00+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/mime", - "version": "v7.4.0", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a" + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/bdb02729471be5d047a3ac4a69068748f1a6be7a", - "reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a", + "url": "https://api.github.com/repos/symfony/mime/zipball/da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1", "shasum": "" }, "require": { @@ -6820,15 +6840,15 @@ }, "conflict": { "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", "symfony/mailer": "<6.4", "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", "symfony/dependency-injection": "^6.4|^7.0|^8.0", "symfony/process": "^6.4|^7.0|^8.0", "symfony/property-access": "^6.4|^7.0|^8.0", @@ -6865,7 +6885,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.0" + "source": "https://github.com/symfony/mime/tree/v7.4.7" }, "funding": [ { @@ -6885,7 +6905,7 @@ "type": "tidelift" } ], - "time": "2025-11-16T10:14:42+00:00" + "time": "2026-03-05T15:24:09+00:00" }, { "name": "symfony/polyfill-ctype", @@ -7638,16 +7658,16 @@ }, { "name": "symfony/process", - "version": "v7.4.0", + "version": "v7.4.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8" + "reference": "608476f4604102976d687c483ac63a79ba18cc97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", - "reference": "7ca8dc2d0dcf4882658313aba8be5d9fd01026c8", + "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", + "reference": "608476f4604102976d687c483ac63a79ba18cc97", "shasum": "" }, "require": { @@ -7679,7 +7699,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.4.0" + "source": "https://github.com/symfony/process/tree/v7.4.5" }, "funding": [ { @@ -7699,25 +7719,25 @@ "type": "tidelift" } ], - "time": "2025-10-16T11:21:06+00:00" + "time": "2026-01-26T15:07:59+00:00" }, { "name": "symfony/property-access", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "537626149d2910ca43eb9ce465654366bf4442f4" + "reference": "fa49bf1ca8fce1ba0e2dba4e4658554cfb9364b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/537626149d2910ca43eb9ce465654366bf4442f4", - "reference": "537626149d2910ca43eb9ce465654366bf4442f4", + "url": "https://api.github.com/repos/symfony/property-access/zipball/fa49bf1ca8fce1ba0e2dba4e4658554cfb9364b1", + "reference": "fa49bf1ca8fce1ba0e2dba4e4658554cfb9364b1", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/property-info": "^6.4|^7.0|^8.0" + "symfony/property-info": "^6.4.32|~7.3.10|^7.4.4|^8.0.4" }, "require-dev": { "symfony/cache": "^6.4|^7.0|^8.0", @@ -7760,7 +7780,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v7.4.0" + "source": "https://github.com/symfony/property-access/tree/v7.4.4" }, "funding": [ { @@ -7780,37 +7800,37 @@ "type": "tidelift" } ], - "time": "2025-09-08T21:14:32+00:00" + "time": "2026-01-05T08:47:25+00:00" }, { "name": "symfony/property-info", - "version": "v7.4.1", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "912aafe70bee5cfd09fec5916fe35b83f04ae6ae" + "reference": "02501d75fd834345da3ecdd8e3200ced39e370f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/912aafe70bee5cfd09fec5916fe35b83f04ae6ae", - "reference": "912aafe70bee5cfd09fec5916fe35b83f04ae6ae", + "url": "https://api.github.com/repos/symfony/property-info/zipball/02501d75fd834345da3ecdd8e3200ced39e370f8", + "reference": "02501d75fd834345da3ecdd8e3200ced39e370f8", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/string": "^6.4|^7.0|^8.0", - "symfony/type-info": "~7.3.8|^7.4.1|^8.0.1" + "symfony/type-info": "^7.4.7|^8.0.7" }, "conflict": { - "phpdocumentor/reflection-docblock": "<5.2", + "phpdocumentor/reflection-docblock": "<5.2|>=7", "phpdocumentor/type-resolver": "<1.5.1", "symfony/cache": "<6.4", "symfony/dependency-injection": "<6.4", "symfony/serializer": "<6.4" }, "require-dev": { - "phpdocumentor/reflection-docblock": "^5.2", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", "phpstan/phpdoc-parser": "^1.0|^2.0", "symfony/cache": "^6.4|^7.0|^8.0", "symfony/dependency-injection": "^6.4|^7.0|^8.0", @@ -7850,7 +7870,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.4.1" + "source": "https://github.com/symfony/property-info/tree/v7.4.7" }, "funding": [ { @@ -7870,20 +7890,20 @@ "type": "tidelift" } ], - "time": "2025-12-05T14:04:53+00:00" + "time": "2026-03-04T15:53:26+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "0101ff8bd0506703b045b1670960302d302a726c" + "reference": "929ffe10bbfbb92e711ac3818d416f9daffee067" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/0101ff8bd0506703b045b1670960302d302a726c", - "reference": "0101ff8bd0506703b045b1670960302d302a726c", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/929ffe10bbfbb92e711ac3818d416f9daffee067", + "reference": "929ffe10bbfbb92e711ac3818d416f9daffee067", "shasum": "" }, "require": { @@ -7938,7 +7958,7 @@ "psr-7" ], "support": { - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.4.0" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.4.4" }, "funding": [ { @@ -7958,20 +7978,20 @@ "type": "tidelift" } ], - "time": "2025-11-13T08:38:49+00:00" + "time": "2026-01-03T23:30:35+00:00" }, { "name": "symfony/routing", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "4720254cb2644a0b876233d258a32bf017330db7" + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/4720254cb2644a0b876233d258a32bf017330db7", - "reference": "4720254cb2644a0b876233d258a32bf017330db7", + "url": "https://api.github.com/repos/symfony/routing/zipball/238d749c56b804b31a9bf3e26519d93b65a60938", + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938", "shasum": "" }, "require": { @@ -8023,7 +8043,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.0" + "source": "https://github.com/symfony/routing/tree/v7.4.6" }, "funding": [ { @@ -8043,7 +8063,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/service-contracts", @@ -8134,16 +8154,16 @@ }, { "name": "symfony/string", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003" + "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d50e862cb0a0e0886f73ca1f31b865efbb795003", - "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003", + "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", + "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", "shasum": "" }, "require": { @@ -8201,7 +8221,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.0" + "source": "https://github.com/symfony/string/tree/v7.4.6" }, "funding": [ { @@ -8221,20 +8241,20 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-02-09T09:33:46+00:00" }, { "name": "symfony/translation", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "2d01ca0da3f092f91eeedb46f24aa30d2fca8f68" + "reference": "1888cf064399868af3784b9e043240f1d89d25ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/2d01ca0da3f092f91eeedb46f24aa30d2fca8f68", - "reference": "2d01ca0da3f092f91eeedb46f24aa30d2fca8f68", + "url": "https://api.github.com/repos/symfony/translation/zipball/1888cf064399868af3784b9e043240f1d89d25ce", + "reference": "1888cf064399868af3784b9e043240f1d89d25ce", "shasum": "" }, "require": { @@ -8301,7 +8321,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.0" + "source": "https://github.com/symfony/translation/tree/v7.4.6" }, "funding": [ { @@ -8321,7 +8341,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "symfony/translation-contracts", @@ -8407,16 +8427,16 @@ }, { "name": "symfony/type-info", - "version": "v7.4.1", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/type-info.git", - "reference": "ac5ab66b21c758df71b7210cf1033d1ac807f202" + "reference": "31f1e40cbf7851c7354281c90eb1b352c4cb8269" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/type-info/zipball/ac5ab66b21c758df71b7210cf1033d1ac807f202", - "reference": "ac5ab66b21c758df71b7210cf1033d1ac807f202", + "url": "https://api.github.com/repos/symfony/type-info/zipball/31f1e40cbf7851c7354281c90eb1b352c4cb8269", + "reference": "31f1e40cbf7851c7354281c90eb1b352c4cb8269", "shasum": "" }, "require": { @@ -8466,7 +8486,7 @@ "type" ], "support": { - "source": "https://github.com/symfony/type-info/tree/v7.4.1" + "source": "https://github.com/symfony/type-info/tree/v7.4.7" }, "funding": [ { @@ -8486,20 +8506,20 @@ "type": "tidelift" } ], - "time": "2025-12-05T14:04:53+00:00" + "time": "2026-03-04T12:49:16+00:00" }, { "name": "symfony/uid", - "version": "v7.4.0", + "version": "v7.4.4", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "2498e9f81b7baa206f44de583f2f48350b90142c" + "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/2498e9f81b7baa206f44de583f2f48350b90142c", - "reference": "2498e9f81b7baa206f44de583f2f48350b90142c", + "url": "https://api.github.com/repos/symfony/uid/zipball/7719ce8aba76be93dfe249192f1fbfa52c588e36", + "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36", "shasum": "" }, "require": { @@ -8544,7 +8564,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.4.0" + "source": "https://github.com/symfony/uid/tree/v7.4.4" }, "funding": [ { @@ -8564,20 +8584,20 @@ "type": "tidelift" } ], - "time": "2025-09-25T11:02:55+00:00" + "time": "2026-01-03T23:30:35+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece" + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/41fd6c4ae28c38b294b42af6db61446594a0dece", - "reference": "41fd6c4ae28c38b294b42af6db61446594a0dece", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/045321c440ac18347b136c63d2e9bf28a2dc0291", + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291", "shasum": "" }, "require": { @@ -8631,7 +8651,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.6" }, "funding": [ { @@ -8651,7 +8671,7 @@ "type": "tidelift" } ], - "time": "2025-10-27T20:36:44+00:00" + "time": "2026-02-15T10:53:20+00:00" }, { "name": "symfony/var-exporter", @@ -8734,6 +8754,149 @@ ], "time": "2025-09-11T10:15:23+00:00" }, + { + "name": "thecodingmachine/safe", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/silasjoisten", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2026-02-04T18:08:13+00:00" + }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "v2.4.0", @@ -8851,26 +9014,26 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.2", + "version": "v5.6.3", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" + "reference": "955e7815d677a3eaa7075231212f2110983adecc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", - "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", + "reference": "955e7815d677a3eaa7075231212f2110983adecc", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.3", + "graham-campbell/result-type": "^1.1.4", "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.3", - "symfony/polyfill-ctype": "^1.24", - "symfony/polyfill-mbstring": "^1.24", - "symfony/polyfill-php80": "^1.24" + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", @@ -8919,7 +9082,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" }, "funding": [ { @@ -8931,7 +9094,7 @@ "type": "tidelift" } ], - "time": "2025-04-30T23:37:27+00:00" + "time": "2025-12-27T19:49:13+00:00" }, { "name": "voku/portable-ascii", @@ -9124,38 +9287,39 @@ "packages-dev": [ { "name": "barryvdh/laravel-ide-helper", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "b106f7ee85f263c4f103eca49e7bf3862c2e5e75" + "reference": "ad7e37676f1ff985d55ef1b6b96a0c0a40f2609a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/b106f7ee85f263c4f103eca49e7bf3862c2e5e75", - "reference": "b106f7ee85f263c4f103eca49e7bf3862c2e5e75", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/ad7e37676f1ff985d55ef1b6b96a0c0a40f2609a", + "reference": "ad7e37676f1ff985d55ef1b6b96a0c0a40f2609a", "shasum": "" }, "require": { "barryvdh/reflection-docblock": "^2.4", "composer/class-map-generator": "^1.0", "ext-json": "*", - "illuminate/console": "^11.15 || ^12", - "illuminate/database": "^11.15 || ^12", - "illuminate/filesystem": "^11.15 || ^12", - "illuminate/support": "^11.15 || ^12", + "illuminate/console": "^11.15 || ^12 || ^13.0", + "illuminate/database": "^11.15 || ^12 || ^13.0", + "illuminate/filesystem": "^11.15 || ^12 || ^13.0", + "illuminate/support": "^11.15 || ^12 || ^13.0", "php": "^8.2" }, "require-dev": { "ext-pdo_sqlite": "*", "friendsofphp/php-cs-fixer": "^3", - "illuminate/config": "^11.15 || ^12", - "illuminate/view": "^11.15 || ^12", + "illuminate/config": "^11.15 || ^12 || ^13.0", + "illuminate/view": "^11.15 || ^12 || ^13.0", + "larastan/larastan": "^3.1", "mockery/mockery": "^1.4", - "orchestra/testbench": "^9.2 || ^10", - "phpunit/phpunit": "^10.5 || ^11.5.3", + "orchestra/testbench": "^9.2 || ^10 || ^11.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5 || ^11.5.3 || ^12.5.12", "spatie/phpunit-snapshot-assertions": "^4 || ^5", - "vimeo/psalm": "^5.4", "vlucas/phpdotenv": "^5" }, "suggest": { @@ -9169,7 +9333,7 @@ ] }, "branch-alias": { - "dev-master": "3.5-dev" + "dev-master": "3.6-dev" } }, "autoload": { @@ -9202,7 +9366,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-ide-helper/issues", - "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.6.1" + "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.7.0" }, "funding": [ { @@ -9214,20 +9378,20 @@ "type": "github" } ], - "time": "2025-12-10T09:11:07+00:00" + "time": "2026-03-17T14:12:51+00:00" }, { "name": "barryvdh/reflection-docblock", - "version": "v2.4.0", + "version": "v2.4.1", "source": { "type": "git", "url": "https://github.com/barryvdh/ReflectionDocBlock.git", - "reference": "d103774cbe7e94ddee7e4870f97f727b43fe7201" + "reference": "4f5ba70c30c81f2ce03a16a9965832cfcc31ed3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/d103774cbe7e94ddee7e4870f97f727b43fe7201", - "reference": "d103774cbe7e94ddee7e4870f97f727b43fe7201", + "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/4f5ba70c30c81f2ce03a16a9965832cfcc31ed3b", + "reference": "4f5ba70c30c81f2ce03a16a9965832cfcc31ed3b", "shasum": "" }, "require": { @@ -9264,9 +9428,9 @@ } ], "support": { - "source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.4.0" + "source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.4.1" }, - "time": "2025-07-17T06:07:30+00:00" + "time": "2026-03-05T20:09:01+00:00" }, { "name": "clue/ndjson-react", @@ -9334,16 +9498,16 @@ }, { "name": "composer/class-map-generator", - "version": "1.7.0", + "version": "1.7.1", "source": { "type": "git", "url": "https://github.com/composer/class-map-generator.git", - "reference": "2373419b7709815ed323ebf18c3c72d03ff4a8a6" + "reference": "8f5fa3cc214230e71f54924bd0197a3bcc705eb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/2373419b7709815ed323ebf18c3c72d03ff4a8a6", - "reference": "2373419b7709815ed323ebf18c3c72d03ff4a8a6", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/8f5fa3cc214230e71f54924bd0197a3bcc705eb1", + "reference": "8f5fa3cc214230e71f54924bd0197a3bcc705eb1", "shasum": "" }, "require": { @@ -9387,7 +9551,7 @@ ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.7.0" + "source": "https://github.com/composer/class-map-generator/tree/1.7.1" }, "funding": [ { @@ -9399,7 +9563,7 @@ "type": "github" } ], - "time": "2025-11-19T10:41:15+00:00" + "time": "2025-12-29T13:15:25+00:00" }, { "name": "composer/pcre", @@ -9804,16 +9968,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.92.0", + "version": "v3.94.2", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "5646c2cd99b7cb4b658ff681fe27069ba86c7280" + "reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/5646c2cd99b7cb4b658ff681fe27069ba86c7280", - "reference": "5646c2cd99b7cb4b658ff681fe27069ba86c7280", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7787ceff91365ba7d623ec410b8f429cdebb4f63", + "reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63", "shasum": "" }, "require": { @@ -9830,7 +9994,7 @@ "react/event-loop": "^1.5", "react/socket": "^1.16", "react/stream": "^1.4", - "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", + "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0", "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", @@ -9844,17 +10008,18 @@ "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" }, "require-dev": { - "facile-it/paraunit": "^1.3.1 || ^2.7", - "infection/infection": "^0.31.0", - "justinrainbow/json-schema": "^6.5", - "keradus/cli-executor": "^2.2", + "facile-it/paraunit": "^1.3.1 || ^2.7.1", + "infection/infection": "^0.32.3", + "justinrainbow/json-schema": "^6.6.4", + "keradus/cli-executor": "^2.3", "mikey179/vfsstream": "^1.6.12", - "php-coveralls/php-coveralls": "^2.9", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6", - "phpunit/phpunit": "^9.6.25 || ^10.5.53 || ^11.5.34", - "symfony/var-dumper": "^5.4.48 || ^6.4.24 || ^7.3.2 || ^8.0", - "symfony/yaml": "^5.4.45 || ^6.4.24 || ^7.3.2 || ^8.0" + "php-coveralls/php-coveralls": "^2.9.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.7", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.7", + "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.51", + "symfony/polyfill-php85": "^1.33", + "symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.4.4 || ^8.0.4", + "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0.1" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -9895,7 +10060,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.92.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.94.2" }, "funding": [ { @@ -9903,7 +10068,7 @@ "type": "github" } ], - "time": "2025-12-12T10:29:19+00:00" + "time": "2026-02-20T16:13:53+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -9958,16 +10123,16 @@ }, { "name": "iamcal/sql-parser", - "version": "v0.6", + "version": "v0.7", "source": { "type": "git", "url": "https://github.com/iamcal/SQLParser.git", - "reference": "947083e2dca211a6f12fb1beb67a01e387de9b62" + "reference": "610392f38de49a44dab08dc1659960a29874c4b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/iamcal/SQLParser/zipball/947083e2dca211a6f12fb1beb67a01e387de9b62", - "reference": "947083e2dca211a6f12fb1beb67a01e387de9b62", + "url": "https://api.github.com/repos/iamcal/SQLParser/zipball/610392f38de49a44dab08dc1659960a29874c4b8", + "reference": "610392f38de49a44dab08dc1659960a29874c4b8", "shasum": "" }, "require-dev": { @@ -9993,46 +10158,46 @@ "description": "MySQL schema parser", "support": { "issues": "https://github.com/iamcal/SQLParser/issues", - "source": "https://github.com/iamcal/SQLParser/tree/v0.6" + "source": "https://github.com/iamcal/SQLParser/tree/v0.7" }, - "time": "2025-03-17T16:59:46+00:00" + "time": "2026-01-28T22:20:33+00:00" }, { "name": "larastan/larastan", - "version": "v3.8.1", + "version": "v3.9.3", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "ff3725291bc4c7e6032b5a54776e3e5104c86db9" + "reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/ff3725291bc4c7e6032b5a54776e3e5104c86db9", - "reference": "ff3725291bc4c7e6032b5a54776e3e5104c86db9", + "url": "https://api.github.com/repos/larastan/larastan/zipball/64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65", + "reference": "64a52bcc5347c89fdf131cb59f96ebfbc8d1ad65", "shasum": "" }, "require": { "ext-json": "*", - "iamcal/sql-parser": "^0.6.0", - "illuminate/console": "^11.44.2 || ^12.4.1", - "illuminate/container": "^11.44.2 || ^12.4.1", - "illuminate/contracts": "^11.44.2 || ^12.4.1", - "illuminate/database": "^11.44.2 || ^12.4.1", - "illuminate/http": "^11.44.2 || ^12.4.1", - "illuminate/pipeline": "^11.44.2 || ^12.4.1", - "illuminate/support": "^11.44.2 || ^12.4.1", + "iamcal/sql-parser": "^0.7.0", + "illuminate/console": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/container": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/contracts": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/database": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/http": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/pipeline": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/support": "^11.44.2 || ^12.4.1 || ^13", "php": "^8.2", "phpstan/phpstan": "^2.1.32" }, "require-dev": { "doctrine/coding-standard": "^13", - "laravel/framework": "^11.44.2 || ^12.7.2", + "laravel/framework": "^11.44.2 || ^12.7.2 || ^13", "mockery/mockery": "^1.6.12", "nikic/php-parser": "^5.4", - "orchestra/canvas": "^v9.2.2 || ^10.0.1", - "orchestra/testbench-core": "^9.12.0 || ^10.1", + "orchestra/canvas": "^v9.2.2 || ^10.0.1 || ^11", + "orchestra/testbench-core": "^9.12.0 || ^10.1 || ^11", "phpstan/phpstan-deprecation-rules": "^2.0.1", - "phpunit/phpunit": "^10.5.35 || ^11.5.15" + "phpunit/phpunit": "^10.5.35 || ^11.5.15 || ^12.5.8" }, "suggest": { "orchestra/testbench": "Using Larastan for analysing a package needs Testbench", @@ -10077,7 +10242,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.8.1" + "source": "https://github.com/larastan/larastan/tree/v3.9.3" }, "funding": [ { @@ -10085,7 +10250,7 @@ "type": "github" } ], - "time": "2025-12-11T16:37:35+00:00" + "time": "2026-02-20T12:07:12+00:00" }, { "name": "laravel/browser-kit-testing", @@ -10646,16 +10811,16 @@ }, { "name": "php-webdriver/webdriver", - "version": "1.15.2", + "version": "1.16.0", "source": { "type": "git", "url": "https://github.com/php-webdriver/php-webdriver.git", - "reference": "998e499b786805568deaf8cbf06f4044f05d91bf" + "reference": "ac0662863aa120b4f645869f584013e4c4dba46a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/998e499b786805568deaf8cbf06f4044f05d91bf", - "reference": "998e499b786805568deaf8cbf06f4044f05d91bf", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/ac0662863aa120b4f645869f584013e4c4dba46a", + "reference": "ac0662863aa120b4f645869f584013e4c4dba46a", "shasum": "" }, "require": { @@ -10664,7 +10829,7 @@ "ext-zip": "*", "php": "^7.3 || ^8.0", "symfony/polyfill-mbstring": "^1.12", - "symfony/process": "^5.0 || ^6.0 || ^7.0" + "symfony/process": "^5.0 || ^6.0 || ^7.0 || ^8.0" }, "replace": { "facebook/webdriver": "*" @@ -10677,10 +10842,10 @@ "php-parallel-lint/php-parallel-lint": "^1.2", "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.5", - "symfony/var-dumper": "^5.0 || ^6.0 || ^7.0" + "symfony/var-dumper": "^5.0 || ^6.0 || ^7.0 || ^8.0" }, "suggest": { - "ext-SimpleXML": "For Firefox profile creation" + "ext-simplexml": "For Firefox profile creation" }, "type": "library", "autoload": { @@ -10706,9 +10871,9 @@ ], "support": { "issues": "https://github.com/php-webdriver/php-webdriver/issues", - "source": "https://github.com/php-webdriver/php-webdriver/tree/1.15.2" + "source": "https://github.com/php-webdriver/php-webdriver/tree/1.16.0" }, - "time": "2024-11-21T15:12:59+00:00" + "time": "2025-12-28T23:57:40+00:00" }, { "name": "phpmd/phpmd", @@ -10795,11 +10960,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.33", + "version": "2.1.42", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f", - "reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1279e1ce86ba768f0780c9d889852b4e02ff40d0", + "reference": "1279e1ce86ba768f0780c9d889852b4e02ff40d0", "shasum": "" }, "require": { @@ -10844,7 +11009,7 @@ "type": "github" } ], - "time": "2025-12-05T10:24:31+00:00" + "time": "2026-03-17T14:58:32+00:00" }, { "name": "phpunit/php-code-coverage", @@ -11169,16 +11334,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.60", + "version": "10.5.63", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f2e26f52f80ef77832e359205f216eeac00e320c" + "reference": "33198268dad71e926626b618f3ec3966661e4d90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f2e26f52f80ef77832e359205f216eeac00e320c", - "reference": "f2e26f52f80ef77832e359205f216eeac00e320c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", + "reference": "33198268dad71e926626b618f3ec3966661e4d90", "shasum": "" }, "require": { @@ -11199,7 +11364,7 @@ "phpunit/php-timer": "^6.0.0", "sebastian/cli-parser": "^2.0.1", "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.4", + "sebastian/comparator": "^5.0.5", "sebastian/diff": "^5.1.1", "sebastian/environment": "^6.1.0", "sebastian/exporter": "^5.1.4", @@ -11250,7 +11415,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.60" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" }, "funding": [ { @@ -11274,7 +11439,7 @@ "type": "tidelift" } ], - "time": "2025-12-06T07:50:42+00:00" + "time": "2026-01-27T05:48:37+00:00" }, { "name": "react/cache", @@ -11350,16 +11515,16 @@ }, { "name": "react/child-process", - "version": "v0.6.6", + "version": "v0.6.7", "source": { "type": "git", "url": "https://github.com/reactphp/child-process.git", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3", + "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3", "shasum": "" }, "require": { @@ -11413,7 +11578,7 @@ ], "support": { "issues": "https://github.com/reactphp/child-process/issues", - "source": "https://github.com/reactphp/child-process/tree/v0.6.6" + "source": "https://github.com/reactphp/child-process/tree/v0.6.7" }, "funding": [ { @@ -11421,7 +11586,7 @@ "type": "open_collective" } ], - "time": "2025-01-01T16:37:48+00:00" + "time": "2025-12-23T15:25:20+00:00" }, { "name": "react/dns", @@ -11972,16 +12137,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.4", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e" + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e", - "reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", "shasum": "" }, "require": { @@ -12037,7 +12202,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" }, "funding": [ { @@ -12057,7 +12222,7 @@ "type": "tidelift" } ], - "time": "2025-09-07T05:25:07+00:00" + "time": "2026-01-24T09:25:16+00:00" }, { "name": "sebastian/complexity", @@ -12757,16 +12922,16 @@ }, { "name": "spatie/backtrace", - "version": "1.8.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110" + "reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/8c0f16a59ae35ec8c62d85c3c17585158f430110", - "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/8ffe78be5ed355b5009e3dd989d183433e9a5adc", + "reference": "8ffe78be5ed355b5009e3dd989d183433e9a5adc", "shasum": "" }, "require": { @@ -12777,7 +12942,7 @@ "laravel/serializable-closure": "^1.3 || ^2.0", "phpunit/phpunit": "^9.3 || ^11.4.3", "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6", - "symfony/var-dumper": "^5.1 || ^6.0 || ^7.0" + "symfony/var-dumper": "^5.1|^6.0|^7.0|^8.0" }, "type": "library", "autoload": { @@ -12805,7 +12970,7 @@ ], "support": { "issues": "https://github.com/spatie/backtrace/issues", - "source": "https://github.com/spatie/backtrace/tree/1.8.1" + "source": "https://github.com/spatie/backtrace/tree/1.8.2" }, "funding": [ { @@ -12817,7 +12982,7 @@ "type": "other" } ], - "time": "2025-08-26T08:22:30+00:00" + "time": "2026-03-11T13:48:28+00:00" }, { "name": "spatie/error-solutions", @@ -12895,26 +13060,26 @@ }, { "name": "spatie/flare-client-php", - "version": "1.10.1", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "bf1716eb98bd689451b071548ae9e70738dce62f" + "reference": "fb3ffb946675dba811fbde9122224db2f84daca9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/bf1716eb98bd689451b071548ae9e70738dce62f", - "reference": "bf1716eb98bd689451b071548ae9e70738dce62f", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/fb3ffb946675dba811fbde9122224db2f84daca9", + "reference": "fb3ffb946675dba811fbde9122224db2f84daca9", "shasum": "" }, "require": { - "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", "php": "^8.0", "spatie/backtrace": "^1.6.1", - "symfony/http-foundation": "^5.2|^6.0|^7.0", - "symfony/mime": "^5.2|^6.0|^7.0", - "symfony/process": "^5.2|^6.0|^7.0", - "symfony/var-dumper": "^5.2|^6.0|^7.0" + "symfony/http-foundation": "^5.2|^6.0|^7.0|^8.0", + "symfony/mime": "^5.2|^6.0|^7.0|^8.0", + "symfony/process": "^5.2|^6.0|^7.0|^8.0", + "symfony/var-dumper": "^5.2|^6.0|^7.0|^8.0" }, "require-dev": { "dms/phpunit-arraysubset-asserts": "^0.5.0", @@ -12952,7 +13117,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.10.1" + "source": "https://github.com/spatie/flare-client-php/tree/1.11.0" }, "funding": [ { @@ -12960,41 +13125,44 @@ "type": "github" } ], - "time": "2025-02-14T13:42:06+00:00" + "time": "2026-03-17T08:06:16+00:00" }, { "name": "spatie/ignition", - "version": "1.15.1", + "version": "1.16.0", "source": { "type": "git", "url": "https://github.com/spatie/ignition.git", - "reference": "31f314153020aee5af3537e507fef892ffbf8c85" + "reference": "b59385bb7aa24dae81bcc15850ebecfda7b40838" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ignition/zipball/31f314153020aee5af3537e507fef892ffbf8c85", - "reference": "31f314153020aee5af3537e507fef892ffbf8c85", + "url": "https://api.github.com/repos/spatie/ignition/zipball/b59385bb7aa24dae81bcc15850ebecfda7b40838", + "reference": "b59385bb7aa24dae81bcc15850ebecfda7b40838", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", "php": "^8.0", - "spatie/error-solutions": "^1.0", - "spatie/flare-client-php": "^1.7", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "spatie/backtrace": "^1.7.1", + "spatie/error-solutions": "^1.1.2", + "spatie/flare-client-php": "^1.9", + "symfony/console": "^5.4.42|^6.0|^7.0|^8.0", + "symfony/http-foundation": "^5.4.42|^6.0|^7.0|^8.0", + "symfony/mime": "^5.4.42|^6.0|^7.0|^8.0", + "symfony/var-dumper": "^5.4.42|^6.0|^7.0|^8.0" }, "require-dev": { - "illuminate/cache": "^9.52|^10.0|^11.0|^12.0", + "illuminate/cache": "^9.52|^10.0|^11.0|^12.0|^13.0", "mockery/mockery": "^1.4", - "pestphp/pest": "^1.20|^2.0", + "pestphp/pest": "^1.20|^2.0|^3.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", "psr/simple-cache-implementation": "*", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", + "symfony/cache": "^5.4.38|^6.0|^7.0|^8.0", + "symfony/process": "^5.4.35|^6.0|^7.0|^8.0", "vlucas/phpdotenv": "^5.5" }, "suggest": { @@ -13043,42 +13211,43 @@ "type": "github" } ], - "time": "2025-02-21T14:31:39+00:00" + "time": "2026-03-17T10:51:08+00:00" }, { "name": "spatie/laravel-ignition", - "version": "2.9.1", + "version": "2.12.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "1baee07216d6748ebd3a65ba97381b051838707a" + "reference": "45b3b6e1e73fc161cba2149972698644b99594ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/1baee07216d6748ebd3a65ba97381b051838707a", - "reference": "1baee07216d6748ebd3a65ba97381b051838707a", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/45b3b6e1e73fc161cba2149972698644b99594ee", + "reference": "45b3b6e1e73fc161cba2149972698644b99594ee", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", - "illuminate/support": "^10.0|^11.0|^12.0", - "php": "^8.1", - "spatie/ignition": "^1.15", - "symfony/console": "^6.2.3|^7.0", - "symfony/var-dumper": "^6.2.3|^7.0" + "illuminate/support": "^11.0|^12.0|^13.0", + "nesbot/carbon": "^2.72|^3.0", + "php": "^8.2", + "spatie/ignition": "^1.16", + "symfony/console": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" }, "require-dev": { - "livewire/livewire": "^2.11|^3.3.5", - "mockery/mockery": "^1.5.1", - "openai-php/client": "^0.8.1|^0.10", - "orchestra/testbench": "8.22.3|^9.0|^10.0", - "pestphp/pest": "^2.34|^3.7", - "phpstan/extension-installer": "^1.3.1", - "phpstan/phpstan-deprecation-rules": "^1.1.1|^2.0", - "phpstan/phpstan-phpunit": "^1.3.16|^2.0", - "vlucas/phpdotenv": "^5.5" + "livewire/livewire": "^3.7.0|^4.0|dev-josh/v3-laravel-13-support", + "mockery/mockery": "^1.6.12", + "openai-php/client": "^0.10.3|^0.19", + "orchestra/testbench": "^v9.16.0|^10.6|^11.0", + "pestphp/pest": "^3.7|^4.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.8", + "vlucas/phpdotenv": "^5.6.2" }, "suggest": { "openai-php/client": "Require get solutions from OpenAI", @@ -13134,30 +13303,30 @@ "type": "github" } ], - "time": "2025-02-20T13:13:55+00:00" + "time": "2026-03-17T12:20:04+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.13.5", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" + "reference": "0525c73950de35ded110cffafb9892946d7771b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5", + "reference": "0525c73950de35ded110cffafb9892946d7771b5", "shasum": "" }, "require": { "ext-simplexml": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": ">=5.4.0" + "php": ">=7.2.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + "phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31" }, "bin": [ "bin/phpcbf", @@ -13182,7 +13351,7 @@ "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.", "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", @@ -13213,20 +13382,20 @@ "type": "thanks_dev" } ], - "time": "2025-11-04T16:30:35+00:00" + "time": "2025-11-10T16:43:36+00:00" }, { "name": "symfony/config", - "version": "v7.4.1", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "2c323304c354a43a48b61c5fa760fc4ed60ce495" + "reference": "6c17162555bfb58957a55bb0e43e00035b6ae3d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/2c323304c354a43a48b61c5fa760fc4ed60ce495", - "reference": "2c323304c354a43a48b61c5fa760fc4ed60ce495", + "url": "https://api.github.com/repos/symfony/config/zipball/6c17162555bfb58957a55bb0e43e00035b6ae3d5", + "reference": "6c17162555bfb58957a55bb0e43e00035b6ae3d5", "shasum": "" }, "require": { @@ -13272,7 +13441,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.4.1" + "source": "https://github.com/symfony/config/tree/v7.4.7" }, "funding": [ { @@ -13292,20 +13461,20 @@ "type": "tidelift" } ], - "time": "2025-12-05T07:52:08+00:00" + "time": "2026-03-06T10:41:14+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.4.2", + "version": "v7.4.7", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "baf614f7c15b30ba6762d4b1ddabdf83dbf0d29b" + "reference": "0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/baf614f7c15b30ba6762d4b1ddabdf83dbf0d29b", - "reference": "baf614f7c15b30ba6762d4b1ddabdf83dbf0d29b", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db", + "reference": "0f651e58f4917fb0e2cd261ccbfe3d71e6e0f5db", "shasum": "" }, "require": { @@ -13356,7 +13525,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.4.2" + "source": "https://github.com/symfony/dependency-injection/tree/v7.4.7" }, "funding": [ { @@ -13376,20 +13545,20 @@ "type": "tidelift" } ], - "time": "2025-12-08T06:57:04+00:00" + "time": "2026-03-03T07:48:48+00:00" }, { "name": "symfony/dom-crawler", - "version": "v7.4.1", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "0c5e8f20c74c78172a8ee72b125909b505033597" + "reference": "487ba8fa43da9a8e6503fe939b45ecd96875410e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/0c5e8f20c74c78172a8ee72b125909b505033597", - "reference": "0c5e8f20c74c78172a8ee72b125909b505033597", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/487ba8fa43da9a8e6503fe939b45ecd96875410e", + "reference": "487ba8fa43da9a8e6503fe939b45ecd96875410e", "shasum": "" }, "require": { @@ -13428,7 +13597,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.4.1" + "source": "https://github.com/symfony/dom-crawler/tree/v7.4.6" }, "funding": [ { @@ -13448,20 +13617,20 @@ "type": "tidelift" } ], - "time": "2025-12-06T15:47:47+00:00" + "time": "2026-02-17T07:53:42+00:00" }, { "name": "symfony/filesystem", - "version": "v7.4.0", + "version": "v7.4.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a" + "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", - "reference": "d551b38811096d0be9c4691d406991b47c0c630a", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/3ebc794fa5315e59fd122561623c2e2e4280538e", + "reference": "3ebc794fa5315e59fd122561623c2e2e4280538e", "shasum": "" }, "require": { @@ -13498,7 +13667,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.4.0" + "source": "https://github.com/symfony/filesystem/tree/v7.4.6" }, "funding": [ { @@ -13518,7 +13687,7 @@ "type": "tidelift" } ], - "time": "2025-11-27T13:27:24+00:00" + "time": "2026-02-25T16:50:00+00:00" }, { "name": "symfony/options-resolver", From 406eb4c7c1f1d1d8234eee76d8761551739965ab Mon Sep 17 00:00:00 2001 From: charles strange Date: Fri, 20 Mar 2026 11:52:35 +0000 Subject: [PATCH 011/168] chore: update more important packages --- composer.json | 16 ++--- composer.lock | 162 +++++++++++++++++++++++++------------------------- 2 files changed, 89 insertions(+), 89 deletions(-) diff --git a/composer.json b/composer.json index d68c6b181..2c9d928e1 100644 --- a/composer.json +++ b/composer.json @@ -26,12 +26,12 @@ "fakerphp/faker": "1.24.1", "highsolutions/laravel-searchy": "12.0", "imtigger/laravel-job-status": "1.2.0", - "laravel/framework": "v11.47.0", - "laravel/helpers": "v1.8.2", - "laravel/legacy-factories": "1.4.1", - "laravel/passport": "v12.4.2", - "laravel/tinker": "2.10.2", - "laravel/ui": "4.6.1", + "laravel/framework": "v11.50.0", + "laravel/helpers": "v1.8.3", + "laravel/legacy-factories": "v1.4.2", + "laravel/passport": "v12.4.3", + "laravel/tinker": "v2.11.1", + "laravel/ui": "4.6.3", "maennchen/zipstream-php": "3.1.2", "neontribe/laravel-specification": "dev-master", "ramsey/uuid": "4.9.2", @@ -47,8 +47,8 @@ "barryvdh/laravel-ide-helper": "v3.7.0", "friendsofphp/php-cs-fixer": "v3.94.2", "larastan/larastan": "v3.9.3", - "laravel/browser-kit-testing": "7.2.6", - "laravel/dusk": "v8.3.4", + "laravel/browser-kit-testing": "v7.2.8", + "laravel/dusk": "v8.4.1", "mockery/mockery": "1.6.12", "nunomaduro/collision": "v8.5.0", "phpmd/phpmd": "2.15.0", diff --git a/composer.lock b/composer.lock index de35272fe..b40def464 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "73e8c169f4f60fa35d79eadf6815070f", + "content-hash": "f0a0f0463624ac7e67cf5592ee04aea7", "packages": [ { "name": "barryvdh/laravel-debugbar", @@ -957,16 +957,16 @@ }, { "name": "firebase/php-jwt", - "version": "v6.11.1", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" + "reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/28aa0694bcfdfa5e2959c394d5a1ee7a5083629e", + "reference": "28aa0694bcfdfa5e2959c394d5a1ee7a5083629e", "shasum": "" }, "require": { @@ -1014,9 +1014,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" + "source": "https://github.com/firebase/php-jwt/tree/v7.0.3" }, - "time": "2025-04-09T20:32:01+00:00" + "time": "2026-02-25T22:16:40+00:00" }, { "name": "fruitcake/php-cors", @@ -1696,16 +1696,16 @@ }, { "name": "laravel/framework", - "version": "v11.47.0", + "version": "v11.50.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "86693ffa1ba32f56f8c44e31416c6665095a62c5" + "reference": "c761f591209b45f56c1317ecbff0b04c89cf7ba2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/86693ffa1ba32f56f8c44e31416c6665095a62c5", - "reference": "86693ffa1ba32f56f8c44e31416c6665095a62c5", + "url": "https://api.github.com/repos/laravel/framework/zipball/c761f591209b45f56c1317ecbff0b04c89cf7ba2", + "reference": "c761f591209b45f56c1317ecbff0b04c89cf7ba2", "shasum": "" }, "require": { @@ -1813,10 +1813,10 @@ "league/flysystem-read-only": "^3.25.1", "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", - "orchestra/testbench-core": "^9.16.1", + "orchestra/testbench-core": "^9.18.0", "pda/pheanstalk": "^5.0.6", "php-http/discovery": "^1.15", - "phpstan/phpstan": "^2.0", + "phpstan/phpstan": "2.1.41", "phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1", "predis/predis": "^2.3", "resend/resend-php": "^0.10.0", @@ -1907,24 +1907,24 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-11-28T18:20:11+00:00" + "time": "2026-03-17T19:35:22+00:00" }, { "name": "laravel/helpers", - "version": "v1.8.2", + "version": "v1.8.3", "source": { "type": "git", "url": "https://github.com/laravel/helpers.git", - "reference": "98499eea4c1cca76fb0fb37ed365a468773daf0a" + "reference": "5915be977c7cc05fe2498d561b8c026ee56567dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/helpers/zipball/98499eea4c1cca76fb0fb37ed365a468773daf0a", - "reference": "98499eea4c1cca76fb0fb37ed365a468773daf0a", + "url": "https://api.github.com/repos/laravel/helpers/zipball/5915be977c7cc05fe2498d561b8c026ee56567dd", + "reference": "5915be977c7cc05fe2498d561b8c026ee56567dd", "shasum": "" }, "require": { - "illuminate/support": "~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", "php": "^7.2.0|^8.0" }, "require-dev": { @@ -1962,26 +1962,26 @@ "laravel" ], "support": { - "source": "https://github.com/laravel/helpers/tree/v1.8.2" + "source": "https://github.com/laravel/helpers/tree/v1.8.3" }, - "time": "2025-11-25T14:46:28+00:00" + "time": "2026-03-17T16:40:11+00:00" }, { "name": "laravel/legacy-factories", - "version": "v1.4.1", + "version": "v1.4.2", "source": { "type": "git", "url": "https://github.com/laravel/legacy-factories.git", - "reference": "cd0f8c77d116bac121e9779fcff1f71801aaac50" + "reference": "fdaadc5c7cc656503704ad87a6d5fad961cc5c8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/legacy-factories/zipball/cd0f8c77d116bac121e9779fcff1f71801aaac50", - "reference": "cd0f8c77d116bac121e9779fcff1f71801aaac50", + "url": "https://api.github.com/repos/laravel/legacy-factories/zipball/fdaadc5c7cc656503704ad87a6d5fad961cc5c8a", + "reference": "fdaadc5c7cc656503704ad87a6d5fad961cc5c8a", "shasum": "" }, "require": { - "illuminate/macroable": "^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/macroable": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", "php": "^7.3|^8.0", "symfony/finder": "^3.4|^4.0|^5.0|^6.0|^7.0" }, @@ -2020,25 +2020,25 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-01-24T15:41:36+00:00" + "time": "2026-02-21T13:29:40+00:00" }, { "name": "laravel/passport", - "version": "v12.4.2", + "version": "v12.4.3", "source": { "type": "git", "url": "https://github.com/laravel/passport.git", - "reference": "65a885607b62d361aedaeb10a946bc6b5a954262" + "reference": "1d2e0170a52f150d5c35c9a6fc1f7ccebcde7626" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/passport/zipball/65a885607b62d361aedaeb10a946bc6b5a954262", - "reference": "65a885607b62d361aedaeb10a946bc6b5a954262", + "url": "https://api.github.com/repos/laravel/passport/zipball/1d2e0170a52f150d5c35c9a6fc1f7ccebcde7626", + "reference": "1d2e0170a52f150d5c35c9a6fc1f7ccebcde7626", "shasum": "" }, "require": { "ext-json": "*", - "firebase/php-jwt": "^6.4", + "firebase/php-jwt": "^6.4|^7.0", "illuminate/auth": "^9.21|^10.0|^11.0|^12.0", "illuminate/console": "^9.21|^10.0|^11.0|^12.0", "illuminate/container": "^9.21|^10.0|^11.0|^12.0", @@ -2096,7 +2096,7 @@ "issues": "https://github.com/laravel/passport/issues", "source": "https://github.com/laravel/passport" }, - "time": "2025-02-12T16:11:33+00:00" + "time": "2026-02-19T14:14:05+00:00" }, { "name": "laravel/prompts", @@ -2220,16 +2220,16 @@ }, { "name": "laravel/tinker", - "version": "v2.10.2", + "version": "v2.11.1", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c" + "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/3bcb5f62d6f837e0f093a601e26badafb127bd4c", - "reference": "3bcb5f62d6f837e0f093a601e26badafb127bd4c", + "url": "https://api.github.com/repos/laravel/tinker/zipball/c9f80cc835649b5c1842898fb043f8cc098dd741", + "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741", "shasum": "" }, "require": { @@ -2238,7 +2238,7 @@ "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", "php": "^7.2.5|^8.0", "psy/psysh": "^0.11.1|^0.12.0", - "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0" + "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0|^8.0" }, "require-dev": { "mockery/mockery": "~1.3.3|^1.4.2", @@ -2280,35 +2280,35 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.10.2" + "source": "https://github.com/laravel/tinker/tree/v2.11.1" }, - "time": "2025-11-20T16:29:12+00:00" + "time": "2026-02-06T14:12:35+00:00" }, { "name": "laravel/ui", - "version": "v4.6.1", + "version": "v4.6.3", "source": { "type": "git", "url": "https://github.com/laravel/ui.git", - "reference": "7d6ffa38d79f19c9b3e70a751a9af845e8f41d88" + "reference": "ff27db15416c1ed8ad9848f5692e47595dd5de27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/7d6ffa38d79f19c9b3e70a751a9af845e8f41d88", - "reference": "7d6ffa38d79f19c9b3e70a751a9af845e8f41d88", + "url": "https://api.github.com/repos/laravel/ui/zipball/ff27db15416c1ed8ad9848f5692e47595dd5de27", + "reference": "ff27db15416c1ed8ad9848f5692e47595dd5de27", "shasum": "" }, "require": { - "illuminate/console": "^9.21|^10.0|^11.0|^12.0", - "illuminate/filesystem": "^9.21|^10.0|^11.0|^12.0", - "illuminate/support": "^9.21|^10.0|^11.0|^12.0", - "illuminate/validation": "^9.21|^10.0|^11.0|^12.0", + "illuminate/console": "^9.21|^10.0|^11.0|^12.0|^13.0", + "illuminate/filesystem": "^9.21|^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^9.21|^10.0|^11.0|^12.0|^13.0", + "illuminate/validation": "^9.21|^10.0|^11.0|^12.0|^13.0", "php": "^8.0", - "symfony/console": "^6.0|^7.0" + "symfony/console": "^6.0|^7.0|^8.0" }, "require-dev": { - "orchestra/testbench": "^7.35|^8.15|^9.0|^10.0", - "phpunit/phpunit": "^9.3|^10.4|^11.5" + "orchestra/testbench": "^7.35|^8.15|^9.0|^10.0|^11.0", + "phpunit/phpunit": "^9.3|^10.4|^11.5|^12.5|^13.0" }, "type": "library", "extra": { @@ -2343,9 +2343,9 @@ "ui" ], "support": { - "source": "https://github.com/laravel/ui/tree/v4.6.1" + "source": "https://github.com/laravel/ui/tree/v4.6.3" }, - "time": "2025-01-28T15:15:29+00:00" + "time": "2026-03-17T13:41:52+00:00" }, { "name": "lcobucci/clock", @@ -10254,33 +10254,33 @@ }, { "name": "laravel/browser-kit-testing", - "version": "v7.2.6", + "version": "v7.2.8", "source": { "type": "git", "url": "https://github.com/laravel/browser-kit-testing.git", - "reference": "bf69ef9746e4daadd83898e07a7b06ec0aee5f06" + "reference": "7741e237dade26274109d1abae419599f51a911c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/browser-kit-testing/zipball/bf69ef9746e4daadd83898e07a7b06ec0aee5f06", - "reference": "bf69ef9746e4daadd83898e07a7b06ec0aee5f06", + "url": "https://api.github.com/repos/laravel/browser-kit-testing/zipball/7741e237dade26274109d1abae419599f51a911c", + "reference": "7741e237dade26274109d1abae419599f51a911c", "shasum": "" }, "require": { "ext-dom": "*", - "laravel/framework": "^10.44|^11.0|^12.0", + "laravel/framework": "^10.44|^11.0|^12.0|^13.0", "mockery/mockery": "^1.0", "php": "^8.2", - "phpunit/phpunit": "^10.4|^11.0.1|^12.0.1", - "symfony/console": "^6.2|^7.0", - "symfony/css-selector": "^6.2|^7.0", - "symfony/dom-crawler": "^6.2|^7.0", - "symfony/http-foundation": "^6.2|^7.0", - "symfony/http-kernel": "^6.2|^7.0" + "phpunit/phpunit": "^10.4|^11.0.1|^12.0.1|^13.0", + "symfony/console": "^6.2|^7.0|^8.0", + "symfony/css-selector": "^6.2|^7.0|^8.0", + "symfony/dom-crawler": "^6.2|^7.0|^8.0", + "symfony/http-foundation": "^6.2|^7.0|^8.0", + "symfony/http-kernel": "^6.2|^7.0|^8.0" }, "require-dev": { "laravel/pint": "^1.17", - "orchestra/testbench-core": "^8.39|^9.17|^10.8" + "orchestra/testbench-core": "^8.39|^9.17|^10.8|^11.0" }, "type": "library", "extra": { @@ -10310,45 +10310,45 @@ ], "support": { "issues": "https://github.com/laravel/browser-kit-testing/issues", - "source": "https://github.com/laravel/browser-kit-testing/tree/v7.2.6" + "source": "https://github.com/laravel/browser-kit-testing/tree/v7.2.8" }, - "time": "2025-11-25T14:44:40+00:00" + "time": "2026-03-16T23:14:18+00:00" }, { "name": "laravel/dusk", - "version": "v8.3.4", + "version": "v8.4.1", "source": { "type": "git", "url": "https://github.com/laravel/dusk.git", - "reference": "33a4211c7b63ffe430bf30ec3c014012dcb6dfa6" + "reference": "3e6548a0e183b408d7af4cf50cd6a50b66061add" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/dusk/zipball/33a4211c7b63ffe430bf30ec3c014012dcb6dfa6", - "reference": "33a4211c7b63ffe430bf30ec3c014012dcb6dfa6", + "url": "https://api.github.com/repos/laravel/dusk/zipball/3e6548a0e183b408d7af4cf50cd6a50b66061add", + "reference": "3e6548a0e183b408d7af4cf50cd6a50b66061add", "shasum": "" }, "require": { "ext-json": "*", "ext-zip": "*", "guzzlehttp/guzzle": "^7.5", - "illuminate/console": "^10.0|^11.0|^12.0", - "illuminate/support": "^10.0|^11.0|^12.0", + "illuminate/console": "^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", "php": "^8.1", "php-webdriver/webdriver": "^1.15.2", - "symfony/console": "^6.2|^7.0", - "symfony/finder": "^6.2|^7.0", - "symfony/process": "^6.2|^7.0", + "symfony/console": "^6.2|^7.0|^8.0", + "symfony/finder": "^6.2|^7.0|^8.0", + "symfony/process": "^6.2|^7.0|^8.0", "vlucas/phpdotenv": "^5.2" }, "require-dev": { - "laravel/framework": "^10.0|^11.0|^12.0", + "laravel/framework": "^10.0|^11.0|^12.0|^13.0", "mockery/mockery": "^1.6", - "orchestra/testbench-core": "^8.19|^9.17|^10.8", + "orchestra/testbench-core": "^8.19|^9.17|^10.8|^11.0", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^10.1|^11.0|^12.0.1", "psy/psysh": "^0.11.12|^0.12", - "symfony/yaml": "^6.2|^7.0" + "symfony/yaml": "^6.2|^7.0|^8.0" }, "suggest": { "ext-pcntl": "Used to gracefully terminate Dusk when tests are running." @@ -10384,9 +10384,9 @@ ], "support": { "issues": "https://github.com/laravel/dusk/issues", - "source": "https://github.com/laravel/dusk/tree/v8.3.4" + "source": "https://github.com/laravel/dusk/tree/v8.4.1" }, - "time": "2025-11-20T16:26:16+00:00" + "time": "2026-03-10T19:59:33+00:00" }, { "name": "mockery/mockery", From a64b8660d7fce4e22d04452912b285b00effd17c Mon Sep 17 00:00:00 2001 From: charles strange Date: Sat, 21 Mar 2026 20:56:12 +0000 Subject: [PATCH 012/168] chore: update yarn --- package.json | 14 +++++++------- yarn.lock | 42 +++++++++++++++++++++--------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 99fc9203b..7d8d40815 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,13 @@ "production": "mix --production" }, "devDependencies": { - "bootstrap-sass": "^3.4.3", - "jquery": "^3.7.1", - "jquery-ui": "^1.14.1", - "laravel-mix": "^6.0.49", - "resolve-url-loader": "^5.0.0", - "sass": "^1.84.0", - "sass-loader": "^16.0.4" + "bootstrap-sass": "3.4.3", + "jquery": "3.7.1", + "jquery-ui": "1.14.2", + "laravel-mix": "6.0.49", + "resolve-url-loader": "5.0.0", + "sass": "^1.98.0", + "sass-loader": "16.0.7" }, "browserslist": [ ">2%", diff --git a/yarn.lock b/yarn.lock index 3888c2c3e..46fcde3dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1694,7 +1694,7 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== -bootstrap-sass@^3.4.3: +bootstrap-sass@3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/bootstrap-sass/-/bootstrap-sass-3.4.3.tgz#742cc8f4286303ae9fe8e4c95237321eae73766c" integrity sha512-vPgFnGMp1jWZZupOND65WS6mkR8rxhJxndT/AcMbqcq1hHMdkcH4sMPhznLzzoHOHkSCrd6J9F8pWBriPCKP2Q== @@ -3153,10 +3153,10 @@ img-loader@^4.0.0: dependencies: loader-utils "^1.1.0" -immutable@^5.0.2: - version "5.1.4" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.4.tgz#e3f8c1fe7b567d56cf26698f31918c241dae8c1f" - integrity sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA== +immutable@^5.1.5: + version "5.1.5" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.5.tgz#93ee4db5c2a9ab42a4a783069f3c5d8847d40165" + integrity sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A== import-fresh@^3.2.1: version "3.3.1" @@ -3323,14 +3323,14 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -jquery-ui@^1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.14.1.tgz#ba342ea3ffff662b787595391f607d923313e040" - integrity sha512-DhzsYH8VeIvOaxwi+B/2BCsFFT5EGjShdzOcm5DssWjtcpGWIMsn66rJciDA6jBruzNiLf1q0KvwMoX1uGNvnQ== +jquery-ui@1.14.2: + version "1.14.2" + resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.14.2.tgz#515288b5c730b720acca6e53a0366827ad834053" + integrity sha512-1gSl7PUjyipa2adSr780Ujk16faicrV7PjPPzPtvWk7tTqBnsqp67NNV9jZK2+BIxUPXWSnIUU/LBCgwgGZE+Q== dependencies: jquery ">=1.12.0 <5.0.0" -"jquery@>=1.12.0 <5.0.0", jquery@^3.7.1: +jquery@3.7.1, "jquery@>=1.12.0 <5.0.0": version "3.7.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg== @@ -3396,7 +3396,7 @@ klona@^2.0.5: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== -laravel-mix@^6.0.49: +laravel-mix@6.0.49: version "6.0.49" resolved "https://registry.yarnpkg.com/laravel-mix/-/laravel-mix-6.0.49.tgz#d718414858045df9d7467245e13fd4b45bc52c15" integrity sha512-bBMFpFjp26XfijPvY5y9zGKud7VqlyOE0OWUcPo3vTBY5asw8LTjafAbee1dhfLz6PWNqDziz69CP78ELSpfKw== @@ -4518,7 +4518,7 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-url-loader@^5.0.0: +resolve-url-loader@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz#ee3142fb1f1e0d9db9524d539cfa166e9314f795" integrity sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg== @@ -4585,20 +4585,20 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass-loader@^16.0.4: - version "16.0.6" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-16.0.6.tgz#913b05607d06c386bc37870494e1e3a3e091fd3b" - integrity sha512-sglGzId5gmlfxNs4gK2U3h7HlVRfx278YK6Ono5lwzuvi1jxig80YiuHkaDBVsYIKFhx8wN7XSCI0M2IDS/3qA== +sass-loader@16.0.7: + version "16.0.7" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-16.0.7.tgz#d1f8723b795805831d41b5825e3d9cd72cb939e7" + integrity sha512-w6q+fRHourZ+e+xA1kcsF27iGM6jdB8teexYCfdUw0sYgcDNeZESnDNT9sUmmPm3ooziwUJXGwZJSTF3kOdBfA== dependencies: neo-async "^2.6.2" -sass@^1.84.0: - version "1.96.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.96.0.tgz#dca45b6b08ae829500f448124afc7c15150bbb34" - integrity sha512-8u4xqqUeugGNCYwr9ARNtQKTOj4KmYiJAVKXf2CTIivTCR51j96htbMKWDru8H5SaQWpyVgTfOF8Ylyf5pun1Q== +sass@^1.98.0: + version "1.98.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.98.0.tgz#924ce85a3745ccaccd976262fdc1bc0c13aa8e57" + integrity sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A== dependencies: chokidar "^4.0.0" - immutable "^5.0.2" + immutable "^5.1.5" source-map-js ">=0.6.2 <2.0.0" optionalDependencies: "@parcel/watcher" "^2.4.1" From f91b6dc971aad81c33b0bd8249124c8a89e23ca9 Mon Sep 17 00:00:00 2001 From: charles strange Date: Sat, 21 Mar 2026 21:20:45 +0000 Subject: [PATCH 013/168] chore: update phpunit to 11.5.50, so numamaduro/collision can be updated --- composer.json | 4 +- composer.lock | 594 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 354 insertions(+), 244 deletions(-) diff --git a/composer.json b/composer.json index 2c9d928e1..2fa040ee5 100644 --- a/composer.json +++ b/composer.json @@ -50,10 +50,10 @@ "laravel/browser-kit-testing": "v7.2.8", "laravel/dusk": "v8.4.1", "mockery/mockery": "1.6.12", - "nunomaduro/collision": "v8.5.0", + "nunomaduro/collision": "v8.9.1", "phpmd/phpmd": "2.15.0", "phpstan/phpstan": "2.1.42", - "phpunit/phpunit": "10.5.63", + "phpunit/phpunit": "11.5.50", "spatie/laravel-ignition": "2.12.0", "squizlabs/php_codesniffer": "4.0.1" }, diff --git a/composer.lock b/composer.lock index b40def464..daaf744a1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f0a0f0463624ac7e67cf5592ee04aea7", + "content-hash": "d6fa7bf2c9ba42a71467abc6ce953861", "packages": [ { "name": "barryvdh/laravel-debugbar", @@ -10533,38 +10533,36 @@ }, { "name": "nunomaduro/collision", - "version": "v8.5.0", + "version": "v8.9.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "f5c101b929c958e849a633283adff296ed5f38f5" + "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f5c101b929c958e849a633283adff296ed5f38f5", - "reference": "f5c101b929c958e849a633283adff296ed5f38f5", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", + "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", "shasum": "" }, "require": { - "filp/whoops": "^2.16.0", - "nunomaduro/termwind": "^2.1.0", + "filp/whoops": "^2.18.4", + "nunomaduro/termwind": "^2.4.0", "php": "^8.2.0", - "symfony/console": "^7.1.5" + "symfony/console": "^7.4.4 || ^8.0.4" }, "conflict": { - "laravel/framework": "<11.0.0 || >=12.0.0", - "phpunit/phpunit": "<10.5.1 || >=12.0.0" + "laravel/framework": "<11.48.0 || >=14.0.0", + "phpunit/phpunit": "<11.5.50 || >=14.0.0" }, "require-dev": { - "larastan/larastan": "^2.9.8", - "laravel/framework": "^11.28.0", - "laravel/pint": "^1.18.1", - "laravel/sail": "^1.36.0", - "laravel/sanctum": "^4.0.3", - "laravel/tinker": "^2.10.0", - "orchestra/testbench-core": "^9.5.3", - "pestphp/pest": "^2.36.0 || ^3.4.0", - "sebastian/environment": "^6.1.0 || ^7.2.0" + "brianium/paratest": "^7.8.5", + "larastan/larastan": "^3.9.2", + "laravel/framework": "^11.48.0 || ^12.52.0", + "laravel/pint": "^1.27.1", + "orchestra/testbench-core": "^9.12.0 || ^10.9.0", + "pestphp/pest": "^3.8.5 || ^4.4.1 || ^5.0.0", + "sebastian/environment": "^7.2.1 || ^8.0.3 || ^9.0.0" }, "type": "library", "extra": { @@ -10601,6 +10599,7 @@ "cli", "command-line", "console", + "dev", "error", "handling", "laravel", @@ -10626,7 +10625,7 @@ "type": "patreon" } ], - "time": "2024-10-15T16:06:32+00:00" + "time": "2026-02-17T17:33:08+00:00" }, { "name": "pdepend/pdepend", @@ -11013,35 +11012,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.16", + "version": "11.0.12", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77" + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=8.1", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-text-template": "^3.0.1", - "sebastian/code-unit-reverse-lookup": "^3.0.0", - "sebastian/complexity": "^3.2.0", - "sebastian/environment": "^6.1.0", - "sebastian/lines-of-code": "^2.0.2", - "sebastian/version": "^4.0.1", - "theseer/tokenizer": "^1.2.3" + "nikic/php-parser": "^5.7.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.1", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.3.1" }, "require-dev": { - "phpunit/phpunit": "^10.1" + "phpunit/phpunit": "^11.5.46" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -11050,7 +11049,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1.x-dev" + "dev-main": "11.0.x-dev" } }, "autoload": { @@ -11079,40 +11078,52 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2024-08-22T04:31:57+00:00" + "time": "2025-12-24T07:01:01+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "4.1.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -11140,36 +11151,48 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" } ], - "time": "2023-08-31T06:24:48+00:00" + "time": "2026-02-02T13:52:54+00:00" }, { "name": "phpunit/php-invoker", - "version": "4.0.0", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-pcntl": "*" @@ -11177,7 +11200,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -11203,7 +11226,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" }, "funding": [ { @@ -11211,32 +11235,32 @@ "type": "github" } ], - "time": "2023-02-03T06:56:09+00:00" + "time": "2024-07-03T05:07:44+00:00" }, { "name": "phpunit/php-text-template", - "version": "3.0.1", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -11263,7 +11287,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" }, "funding": [ { @@ -11271,32 +11295,32 @@ "type": "github" } ], - "time": "2023-08-31T14:07:24+00:00" + "time": "2024-07-03T05:08:43+00:00" }, { "name": "phpunit/php-timer", - "version": "6.0.0", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -11322,7 +11346,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" }, "funding": [ { @@ -11330,20 +11355,20 @@ "type": "github" } ], - "time": "2023-02-03T06:57:52+00:00" + "time": "2024-07-03T05:09:35+00:00" }, { "name": "phpunit/phpunit", - "version": "10.5.63", + "version": "11.5.50", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "33198268dad71e926626b618f3ec3966661e4d90" + "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90", - "reference": "33198268dad71e926626b618f3ec3966661e4d90", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", + "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", "shasum": "" }, "require": { @@ -11356,23 +11381,23 @@ "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", - "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.16", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-invoker": "^4.0.0", - "phpunit/php-text-template": "^3.0.1", - "phpunit/php-timer": "^6.0.0", - "sebastian/cli-parser": "^2.0.1", - "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.5", - "sebastian/diff": "^5.1.1", - "sebastian/environment": "^6.1.0", - "sebastian/exporter": "^5.1.4", - "sebastian/global-state": "^6.0.2", - "sebastian/object-enumerator": "^5.0.0", - "sebastian/recursion-context": "^5.0.1", - "sebastian/type": "^4.0.0", - "sebastian/version": "^4.0.1" + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.3", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.3", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -11383,7 +11408,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.5-dev" + "dev-main": "11.5-dev" } }, "autoload": { @@ -11415,7 +11440,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.50" }, "funding": [ { @@ -11439,7 +11464,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T05:48:37+00:00" + "time": "2026-01-27T05:59:18+00:00" }, { "name": "react/cache", @@ -11969,28 +11994,28 @@ }, { "name": "sebastian/cli-parser", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -12014,7 +12039,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" }, "funding": [ { @@ -12022,32 +12047,32 @@ "type": "github" } ], - "time": "2024-03-02T07:12:49+00:00" + "time": "2024-07-03T04:41:36+00:00" }, { "name": "sebastian/code-unit", - "version": "2.0.0", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -12070,7 +12095,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" }, "funding": [ { @@ -12078,32 +12104,32 @@ "type": "github" } ], - "time": "2023-02-03T06:58:43+00:00" + "time": "2025-03-19T07:56:08+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -12125,7 +12151,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" }, "funding": [ { @@ -12133,36 +12160,39 @@ "type": "github" } ], - "time": "2023-02-03T06:59:15+00:00" + "time": "2024-07-03T04:45:54+00:00" }, { "name": "sebastian/comparator", - "version": "5.0.5", + "version": "6.3.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d" + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d", - "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", "shasum": "" }, "require": { "ext-dom": "*", "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -12202,7 +12232,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3" }, "funding": [ { @@ -12222,33 +12252,33 @@ "type": "tidelift" } ], - "time": "2026-01-24T09:25:16+00:00" + "time": "2026-01-24T09:26:40+00:00" }, { "name": "sebastian/complexity", - "version": "3.2.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -12272,7 +12302,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" }, "funding": [ { @@ -12280,33 +12310,33 @@ "type": "github" } ], - "time": "2023-12-21T08:37:17+00:00" + "time": "2024-07-03T04:49:50+00:00" }, { "name": "sebastian/diff", - "version": "5.1.1", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0", - "symfony/process": "^6.4" + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -12339,7 +12369,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" }, "funding": [ { @@ -12347,27 +12377,27 @@ "type": "github" } ], - "time": "2024-03-02T07:15:17+00:00" + "time": "2024-07-03T04:53:05+00:00" }, { "name": "sebastian/environment", - "version": "6.1.0", + "version": "7.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.3" }, "suggest": { "ext-posix": "*" @@ -12375,7 +12405,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-main": "7.2-dev" } }, "autoload": { @@ -12403,42 +12433,54 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" } ], - "time": "2024-03-23T08:47:14+00:00" + "time": "2025-05-21T11:55:47+00:00" }, { "name": "sebastian/exporter", - "version": "5.1.4", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "0735b90f4da94969541dac1da743446e276defa6" + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6", - "reference": "0735b90f4da94969541dac1da743446e276defa6", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -12481,7 +12523,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.4" + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" }, "funding": [ { @@ -12501,35 +12543,35 @@ "type": "tidelift" } ], - "time": "2025-09-24T06:09:11+00:00" + "time": "2025-09-24T06:12:51+00:00" }, { "name": "sebastian/global-state", - "version": "6.0.2", + "version": "7.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -12555,7 +12597,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" }, "funding": [ { @@ -12563,33 +12605,33 @@ "type": "github" } ], - "time": "2024-03-02T07:19:19+00:00" + "time": "2024-07-03T04:57:36+00:00" }, { "name": "sebastian/lines-of-code", - "version": "2.0.2", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -12613,7 +12655,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" }, "funding": [ { @@ -12621,34 +12663,34 @@ "type": "github" } ], - "time": "2023-12-21T08:38:20+00:00" + "time": "2024-07-03T04:58:38+00:00" }, { "name": "sebastian/object-enumerator", - "version": "5.0.0", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -12670,7 +12712,8 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" }, "funding": [ { @@ -12678,32 +12721,32 @@ "type": "github" } ], - "time": "2023-02-03T07:08:32+00:00" + "time": "2024-07-03T05:00:13+00:00" }, { "name": "sebastian/object-reflector", - "version": "3.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -12725,7 +12768,8 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" }, "funding": [ { @@ -12733,32 +12777,32 @@ "type": "github" } ], - "time": "2023-02-03T07:06:18+00:00" + "time": "2024-07-03T05:01:32+00:00" }, { "name": "sebastian/recursion-context", - "version": "5.0.1", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a" + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a", - "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.5" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -12789,7 +12833,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" }, "funding": [ { @@ -12809,32 +12853,32 @@ "type": "tidelift" } ], - "time": "2025-08-10T07:50:56+00:00" + "time": "2025-08-13T04:42:22+00:00" }, { "name": "sebastian/type", - "version": "4.0.0", + "version": "5.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -12857,37 +12901,50 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2023-02-03T07:10:45+00:00" + "time": "2025-08-09T06:55:48+00:00" }, { "name": "sebastian/version", - "version": "4.0.1", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -12910,7 +12967,8 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" }, "funding": [ { @@ -12918,7 +12976,7 @@ "type": "github" } ], - "time": "2023-02-07T11:34:05+00:00" + "time": "2024-10-09T05:16:32+00:00" }, { "name": "spatie/backtrace", @@ -13384,6 +13442,58 @@ ], "time": "2025-11-10T16:43:36+00:00" }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, { "name": "symfony/config", "version": "v7.4.7", From a71f6828215c400e993f4534b4c74f9fcdacc916 Mon Sep 17 00:00:00 2001 From: charles strange Date: Sat, 21 Mar 2026 23:50:25 +0000 Subject: [PATCH 014/168] chore: update phpunit tests to be compatible with phpunit 11 --- tests/Browser/AddWorkersJSTest.php | 8 +- tests/Browser/AdminAddTradersTest.php | 9 +- tests/Browser/AdminEditWorkersJSTest.php | 8 +- tests/Browser/AdminViewCentresTest.php | 11 +- tests/Browser/AdminViewWorkersTest.php | 11 +- tests/Browser/ExampleTest.php | 2 +- tests/Browser/Pages/AdminLogin.php | 8 +- tests/Browser/Pages/StoreLogin.php | 10 +- tests/Browser/StoreAddRegistrationTest.php | 8 +- tests/Browser/StoreFamilySearchTest.php | 14 +- tests/Console/Commands/AddCentreTest.php | 14 +- tests/Console/Commands/AddSponsorTest.php | 10 +- .../Console/Commands/ArcTestCoverageTest.php | 9 +- .../CreateMasterVoucherLogReportTest.php | 1 - tests/Console/Commands/MvlExportTest.php | 1 - tests/Console/KernelTest.php | 1 - tests/CreatesApplication.php | 7 +- tests/DuskTestCase.php | 5 +- tests/Feature/Service/AdminResetTest.php | 42 ++-- .../Feature/Service/CreateMarketsPageTest.php | 14 +- .../Feature/Service/CreateTradersPageTest.php | 12 +- tests/Feature/Service/EditMarketsPageTest.php | 16 +- tests/Feature/Service/EditTradersPageTest.php | 12 +- tests/Feature/Service/EditWorkerPageTest.php | 4 +- tests/Feature/Service/LoggingTest.php | 6 +- tests/Feature/Service/MarketsPageTest.php | 20 +- tests/Feature/Service/PaymentsPageTest.php | 29 ++- .../Service/ServiceLiveVouchersPageTest.php | 20 +- .../Service/ServiceWorkersPageTest.php | 12 +- tests/Feature/Service/SessionCookiesTest.php | 5 +- .../Service/TraderPaymentHistoryPageTest.php | 9 +- tests/Feature/Service/TradersPageTest.php | 9 +- .../Service/VoucherDeliveryPageTest.php | 3 +- tests/Feature/Store/DashboardPageTest.php | 16 +- tests/Feature/Store/EditPageTest.php | 193 +++++++++--------- .../Feature/Store/ForgotPasswordPageTest.php | 17 +- tests/Feature/Store/HistoryPageTest.php | 3 +- tests/Feature/Store/LoginPageTest.php | 59 +++--- tests/Feature/Store/RegistrationPageTest.php | 69 +++---- tests/Feature/Store/RejoinPageTest.php | 28 +-- tests/Feature/Store/SearchPageTest.php | 56 ++--- tests/Feature/Store/SessionCookiesTest.php | 5 +- tests/Feature/Store/VoucherManagerTest.php | 33 +-- tests/MysqlStoreTestCase.php | 3 +- tests/StoreTestCase.php | 2 +- .../Api/ApiVoucherControllerTest.php | 20 +- .../Controllers/Api/TraderControllerTest.php | 39 ++-- .../Service/Admin/CentreControllerTest.php | 69 +++---- .../Admin/DeliveriesControllerTest.php | 12 +- .../Service/Admin/PaymentControllerTest.php | 13 +- .../Service/Admin/SponsorControllerTest.php | 28 +-- .../Admin/VoucherControllerMysqlTest.php | 30 +-- .../Service/Admin/VoucherControllerTest.php | 12 +- .../Store/BundleControllerTest.php | 68 +++--- .../Store/StoreVoucherControllerTest.php | 3 +- .../Unit/Exceptions/ExceptionHandlerTest.php | 13 +- .../AdminNewCentreRequestTest.php | 5 +- .../AdminNewCentreUserRequestTest.php | 3 +- .../AdminNewUpdateMarketRequestTest.php | 5 +- .../AdminNewUpdateTraderRequestTest.php | 5 +- .../AdminUpdateVoucherRequestTest.php | 5 +- .../ApiTransitionVoucherRequestTest.php | 7 +- .../StoreNewRegistrationRequestTest.php | 8 +- .../StoreUpdateRegistrationRequestTest.php | 8 +- .../Listeners/CentreUserAuthenticatedTest.php | 24 +-- tests/Unit/Middleware/Empty304Test.php | 3 +- tests/Unit/Models/BundleModelTest.php | 24 +-- tests/Unit/Models/CarerModelTest.php | 9 +- tests/Unit/Models/CentreModelTest.php | 24 +-- tests/Unit/Models/CentreUserModelTest.php | 20 +- tests/Unit/Models/ChildModelTest.php | 19 +- tests/Unit/Models/DeliveryModelTest.php | 20 +- tests/Unit/Models/FamilyModelTest.php | 30 +-- tests/Unit/Models/MarketModelTest.php | 10 +- tests/Unit/Models/RegistrationModelTest.php | 14 +- tests/Unit/Models/SponsorModelTest.php | 18 +- tests/Unit/Models/StateTokenModelTest.php | 8 +- tests/Unit/Models/TraderModelTest.php | 14 +- tests/Unit/Models/UserModelTest.php | 6 +- tests/Unit/Models/VoucherModelTest.php | 67 +++--- tests/Unit/Models/VoucherStateModelTest.php | 65 +++--- tests/Unit/Passport/RoutesTest.php | 18 +- .../MandrillMailServiceProviderTest.php | 14 +- tests/Unit/Routes/ApiRoutesTest.php | 54 ++--- tests/Unit/Routes/DataRoutesTest.php | 16 +- tests/Unit/Routes/ServiceRoutesTest.php | 14 +- tests/Unit/Routes/StoreRoutesTest.php | 59 +++--- tests/Unit/Rules/NotExistsRuleTest.php | 6 +- .../SPVoucherEvaluatorTest.php | 32 +-- .../ScottishVoucherEvaluatorTest.php | 36 ++-- .../VoucherEvaluator/VoucherEvaluatorTest.php | 48 ++--- tests/Unit/Traits/AliasableTraitTest.php | 9 +- 92 files changed, 903 insertions(+), 965 deletions(-) diff --git a/tests/Browser/AddWorkersJSTest.php b/tests/Browser/AddWorkersJSTest.php index 1ceb2ce04..84d1b466e 100644 --- a/tests/Browser/AddWorkersJSTest.php +++ b/tests/Browser/AddWorkersJSTest.php @@ -11,10 +11,10 @@ class AddWorkersJSTest extends DuskTestCase { use RefreshDatabase; - /** @test */ - public function add_workers_javascript_is_working() + + public function testAddWorkersJavascriptIsWorking(): void { - $adminLogin = new AdminLogin; + $adminLogin = new AdminLogin(); $this->browse(function ($browser) use ($adminLogin) { $browser->visit($adminLogin) @@ -27,7 +27,7 @@ public function add_workers_javascript_is_working() ->assertSee('Set Neighbours as Alternatives') ->assertPresent('#neighbour-' . $adminLogin->centres[1]->id) ->assertMissing('#neighbour-' . $adminLogin->other_centres[1]->id) - ; + ; }); } } diff --git a/tests/Browser/AdminAddTradersTest.php b/tests/Browser/AdminAddTradersTest.php index dcf1e1bde..eed4f3232 100644 --- a/tests/Browser/AdminAddTradersTest.php +++ b/tests/Browser/AdminAddTradersTest.php @@ -11,10 +11,10 @@ class AdminAddTradersTest extends DuskTestCase { use RefreshDatabase; - /** @test */ - public function add_traders_javascript_is_working() + + public function testAddTradersJavascriptIsWorking(): void { - $adminLogin = new AdminLogin; + $adminLogin = new AdminLogin(); $this->browse(function ($browser) use ($adminLogin) { $browser->visit($adminLogin) @@ -33,8 +33,7 @@ public function add_traders_javascript_is_working() ->press('.glyphicon-minus') ->assertMissing('@trader_name') ->assertMissing('@trader_email') - ; - + ; }); } } diff --git a/tests/Browser/AdminEditWorkersJSTest.php b/tests/Browser/AdminEditWorkersJSTest.php index fff81a24e..a8c771c63 100644 --- a/tests/Browser/AdminEditWorkersJSTest.php +++ b/tests/Browser/AdminEditWorkersJSTest.php @@ -11,10 +11,10 @@ class AdminEditWorkersJSTest extends DuskTestCase { use RefreshDatabase; - /** @test */ - public function edit_workers_javascript_is_working() + + public function testEditWorkersJavascriptIsWorking(): void { - $adminLogin = new AdminLogin; + $adminLogin = new AdminLogin(); $this->browse(function ($browser) use ($adminLogin) { $browser->visit($adminLogin) @@ -31,7 +31,7 @@ public function edit_workers_javascript_is_working() ->assertPresent('#neighbour-' . $adminLogin->other_centres[2]->id) ->press('Delete worker') ->assertDialogOpened('Are you sure you want to delete this worker account?') - ; + ; }); } } diff --git a/tests/Browser/AdminViewCentresTest.php b/tests/Browser/AdminViewCentresTest.php index bf0469571..47b1cca14 100644 --- a/tests/Browser/AdminViewCentresTest.php +++ b/tests/Browser/AdminViewCentresTest.php @@ -13,8 +13,8 @@ class AdminViewCentresTest extends DuskTestCase { use RefreshDatabase; - /** @test */ - public function the_view_centres_datatable_is_functioning() + + public function testTheViewCentresDatatableIsFunctioning(): void { $sponsor = factory(Sponsor::class) ->create(); @@ -44,17 +44,17 @@ public function the_view_centres_datatable_is_functioning() ->assertSee('Next') ->resize(1920, 3000) ->assertSee('Showing 1 to 10 of 15 entries') - ; + ; $this->assertCount(10, $browser->elements('td.sorting_1')); $browser->resize(1920, 3000) ->click('#centresTable_next') - ; + ; $this->assertCount(5, $browser->elements('td.sorting_1')); $browser->resize(1920, 3000) ->click('#centresTable_previous') - ; + ; $this->assertCount(10, $browser->elements('td.sorting_1')); $browser->select('centresTable_length', '100'); $this->assertCount(15, $browser->elements('td.sorting_1')); @@ -64,7 +64,6 @@ public function the_view_centres_datatable_is_functioning() $browser->assertDontSee($centres[1]->name); $browser->resize(1920, 3000) ->assertSee('Showing 1 to 1 of 1 entries (filtered from 15 total entries)'); - }); } } diff --git a/tests/Browser/AdminViewWorkersTest.php b/tests/Browser/AdminViewWorkersTest.php index c4a785682..e69c699da 100644 --- a/tests/Browser/AdminViewWorkersTest.php +++ b/tests/Browser/AdminViewWorkersTest.php @@ -14,8 +14,8 @@ class AdminViewWorkersTest extends DuskTestCase { use RefreshDatabase; - /** @test */ - public function the_view_workers_datatable_is_functioning() + + public function testTheWiewWorkersDatatableIsFunctioning(): void { $sponsor = factory(Sponsor::class) ->create(); @@ -50,17 +50,17 @@ public function the_view_workers_datatable_is_functioning() ->assertSee('Next') ->resize(1920, 3000) ->assertSee('Showing 1 to 10 of 15 entries') - ; + ; $this->assertCount(10, $browser->elements('td.sorting_1')); $browser->resize(1920, 3000) ->click('#workersTable_next') - ; + ; $this->assertCount(5, $browser->elements('td.sorting_1')); $browser->resize(1920, 3000) ->click('#workersTable_previous') - ; + ; $this->assertCount(10, $browser->elements('td.sorting_1')); $browser->select('workersTable_length', '100'); $this->assertCount(15, $browser->elements('td.sorting_1')); @@ -70,7 +70,6 @@ public function the_view_workers_datatable_is_functioning() $browser->assertDontSee($centreUsers[1]->name); $browser->resize(1920, 3000) ->assertSee('Showing 1 to 1 of 1 entries (filtered from 15 total entries)'); - }); } } diff --git a/tests/Browser/ExampleTest.php b/tests/Browser/ExampleTest.php index 563a21859..436ad813d 100644 --- a/tests/Browser/ExampleTest.php +++ b/tests/Browser/ExampleTest.php @@ -9,7 +9,7 @@ class ExampleTest extends DuskTestCase { use RefreshDatabase; - + /** * A basic browser test example. */ diff --git a/tests/Browser/Pages/AdminLogin.php b/tests/Browser/Pages/AdminLogin.php index 82184bfba..631a452fb 100644 --- a/tests/Browser/Pages/AdminLogin.php +++ b/tests/Browser/Pages/AdminLogin.php @@ -59,7 +59,7 @@ public function __construct() * * @return string */ - public function url() + public function url(): string { return '/login'; } @@ -70,7 +70,7 @@ public function url() * @param Browser $browser * @return void */ - public function assert(Browser $browser) + public function assert(Browser $browser): void { $browser->assertPathIs($this->url()) ->assertSee('E-Mail Address') @@ -85,10 +85,10 @@ public function assert(Browser $browser) * * @return array */ - public function elements() + public function elements(): array { return [ '@element' => '#selector', ]; } -} \ No newline at end of file +} diff --git a/tests/Browser/Pages/StoreLogin.php b/tests/Browser/Pages/StoreLogin.php index 5471f9b97..f805b92e5 100644 --- a/tests/Browser/Pages/StoreLogin.php +++ b/tests/Browser/Pages/StoreLogin.php @@ -67,7 +67,7 @@ public function __construct() * * @return string */ - public function url() + public function url(): string { return 'http://arcv-store.test/login'; } @@ -78,7 +78,7 @@ public function url() * @param Browser $browser * @return void */ - public function assert(Browser $browser) + public function assert(Browser $browser): void { $browser->assertPathIs('/login') ->assertSee('Email Address') @@ -88,7 +88,7 @@ public function assert(Browser $browser) ->press('Log In') ->assertPathIs('/dashboard') ->assertSee('Main menu') - ; + ; } /** @@ -96,10 +96,10 @@ public function assert(Browser $browser) * * @return array */ - public function elements() + public function elements(): array { return [ '@element' => '#selector', ]; } -} \ No newline at end of file +} diff --git a/tests/Browser/StoreAddRegistrationTest.php b/tests/Browser/StoreAddRegistrationTest.php index 2ba5e465f..ea1288c1c 100644 --- a/tests/Browser/StoreAddRegistrationTest.php +++ b/tests/Browser/StoreAddRegistrationTest.php @@ -12,10 +12,10 @@ class StoreAddRegistrationTest extends DuskTestCase { use RefreshDatabase; - /** @test */ - public function the_JS_is_working_correctly_on_the_create_reg_page() + + public function testTheJSIsWorkingCorrectlyOnTheCreateRegPage(): void { - $storeLogin = new StoreLogin; + $storeLogin = new StoreLogin(); $age_now = Carbon::now()->subMonths(27)->diff(Carbon::now())->format('%y yr, %m mo'); $pregnancy = Carbon::now()->addMonths(6); @@ -51,7 +51,7 @@ public function the_JS_is_working_correctly_on_the_create_reg_page() ->assertSeeIn('@pregnancy_col', 'P') ->assertSee($pregnancy->format('M') . ' ' . $pregnancy->format('Y')) ->assertChecked('@create_child_dob') - ; + ; }); } } diff --git a/tests/Browser/StoreFamilySearchTest.php b/tests/Browser/StoreFamilySearchTest.php index 346a66ecb..acc8b493a 100644 --- a/tests/Browser/StoreFamilySearchTest.php +++ b/tests/Browser/StoreFamilySearchTest.php @@ -11,10 +11,10 @@ class StoreFamilySearchTest extends DuskTestCase { use RefreshDatabase; - /** @test */ - public function family_search_datatable_is_working() + + public function testFamilySearchDatatableIsWorking(): void { - $storeLogin = new StoreLogin; + $storeLogin = new StoreLogin(); $pri_carer = $storeLogin->registrations[0]->family->carers->first()->name; $another_pri_carer = $storeLogin->registrations[1]->family->carers->first()->name; @@ -27,17 +27,17 @@ public function family_search_datatable_is_working() ->assertSee('Next') ->resize(1920, 3000) ->assertSee('Showing 1 to 10 of 15 entries') - ; + ; $this->assertCount(10, $browser->elements('td.sorting_1')); $browser->resize(1920, 3000) ->click('#registrationTable_next') - ; + ; $this->assertCount(5, $browser->elements('td.sorting_1')); $browser->resize(1920, 3000) ->click('#registrationTable_previous') - ; + ; $this->assertCount(10, $browser->elements('td.sorting_1')); $browser->select('registrationTable_length', '100'); $this->assertCount(15, $browser->elements('td.sorting_1')); @@ -47,7 +47,7 @@ public function family_search_datatable_is_working() $browser->assertDontSee($another_pri_carer); $browser->resize(1920, 3000) ->assertSee('Showing 1 to 1 of 1 entries (filtered from 15 total entries)') - ; + ; }); } } diff --git a/tests/Console/Commands/AddCentreTest.php b/tests/Console/Commands/AddCentreTest.php index a612b9866..99b85ca0c 100644 --- a/tests/Console/Commands/AddCentreTest.php +++ b/tests/Console/Commands/AddCentreTest.php @@ -35,7 +35,7 @@ public function setUp(): void $this->sponsor = factory(Sponsor::class)->create(); } - public function testCommandOk() + public function testCommandOk(): void { $results = $this ->artisan("arc:addCentre " . @@ -52,7 +52,7 @@ public function testCommandOk() $this->assertEquals(0, $results); } - public function testCommandNoUser() + public function testCommandNoUser(): void { $results = $this ->artisan("arc:addCentre " . @@ -68,7 +68,7 @@ public function testCommandNoUser() $this->assertEquals(1, $results); } - public function testCommandNoSponsor() + public function testCommandNoSponsor(): void { $results = $this ->artisan("arc:addCentre " . @@ -84,7 +84,7 @@ public function testCommandNoSponsor() $this->assertEquals(2, $results); } - public function testCommandCenterExists() + public function testCommandCenterExists(): void { $results = $this ->artisan("arc:addCentre " . @@ -100,7 +100,7 @@ public function testCommandCenterExists() $this->assertEquals(3, $results); } - public function testCommandPreferenceDoesNotExist() + public function testCommandPreferenceDoesNotExist(): void { $results = $this ->artisan("arc:addCentre " . @@ -116,7 +116,7 @@ public function testCommandPreferenceDoesNotExist() $this->assertEquals(4, $results); } - public function testCommandUserWarningDenied() + public function testCommandUserWarningDenied(): void { $results = $this ->artisan("arc:addCentre " . @@ -133,7 +133,7 @@ public function testCommandUserWarningDenied() $this->assertEquals(5, $results); } - public function testCommandFailedLoggedIn() + public function testCommandFailedLoggedIn(): void { Auth::shouldReceive('login')->once(); Auth::shouldReceive('check')->once()->andreturn(false); diff --git a/tests/Console/Commands/AddSponsorTest.php b/tests/Console/Commands/AddSponsorTest.php index cb20f441f..36c483dbe 100644 --- a/tests/Console/Commands/AddSponsorTest.php +++ b/tests/Console/Commands/AddSponsorTest.php @@ -32,7 +32,7 @@ public function setUp(): void $this->sponsor = factory(Sponsor::class)->create(); } - public function testCommandOk() + public function testCommandOk(): void { $results = $this ->artisan("arc:addSponsor " . @@ -47,7 +47,7 @@ public function testCommandOk() $this->assertEquals(0, $results); } - public function testCommandNoUser() + public function testCommandNoUser(): void { $results = $this ->artisan("arc:addSponsor " . @@ -61,7 +61,7 @@ public function testCommandNoUser() $this->assertEquals(1, $results); } - public function testCommandNoSponsor() + public function testCommandNoSponsor(): void { $results = $this ->artisan("arc:addSponsor " . @@ -76,7 +76,7 @@ public function testCommandNoSponsor() } - public function testCommandUserWarningDenied() + public function testCommandUserWarningDenied(): void { $results = $this ->artisan("arc:addSponsor " . @@ -91,7 +91,7 @@ public function testCommandUserWarningDenied() $this->assertEquals(3, $results); } - public function testCommandFailedLoggedIn() + public function testCommandFailedLoggedIn(): void { Auth::shouldReceive('login')->once(); Auth::shouldReceive('check')->once()->andreturn(false); diff --git a/tests/Console/Commands/ArcTestCoverageTest.php b/tests/Console/Commands/ArcTestCoverageTest.php index 0fd11c679..5c1541588 100644 --- a/tests/Console/Commands/ArcTestCoverageTest.php +++ b/tests/Console/Commands/ArcTestCoverageTest.php @@ -9,10 +9,9 @@ class ArcTestCoverageTest extends TestCase { - use CreatesApplication; - public function testCommandOK() + public function testCommandOK(): void { $data = "" . "" . @@ -24,13 +23,13 @@ public function testCommandOK() $this->assertEquals(0, $results); } - public function testCommandNoArg() + public function testCommandNoArg(): void { $this->expectException(\Symfony\Component\Console\Exception\RuntimeException::class); $this->artisan("arc:test:coverage")->execute(); } - public function testCommandFails() + public function testCommandFails(): void { $data = "" . "" . @@ -42,7 +41,7 @@ public function testCommandFails() $this->assertEquals(-1, $results); } - public function testCommandTestDifferentAcceptance() + public function testCommandTestDifferentAcceptance(): void { $data = "" . "" . diff --git a/tests/Console/Commands/CreateMasterVoucherLogReportTest.php b/tests/Console/Commands/CreateMasterVoucherLogReportTest.php index 313602763..0b214caa3 100644 --- a/tests/Console/Commands/CreateMasterVoucherLogReportTest.php +++ b/tests/Console/Commands/CreateMasterVoucherLogReportTest.php @@ -16,5 +16,4 @@ public function testCommandOk(): void ->artisan("arc:createMVLReport", ["--force" => true]); $this->assertEquals(0, $results); } - } diff --git a/tests/Console/Commands/MvlExportTest.php b/tests/Console/Commands/MvlExportTest.php index 3741cd7b8..bf74dd3fc 100644 --- a/tests/Console/Commands/MvlExportTest.php +++ b/tests/Console/Commands/MvlExportTest.php @@ -31,5 +31,4 @@ public function testParameters(): void $this->assertStringContainsString("2023/03/31", $result); $this->assertStringContainsString("54321", $result); } - } diff --git a/tests/Console/KernelTest.php b/tests/Console/KernelTest.php index f48089bd0..8a66f8c8b 100644 --- a/tests/Console/KernelTest.php +++ b/tests/Console/KernelTest.php @@ -9,7 +9,6 @@ class KernelTest extends TestCase { - public function createApplication(): Application { $app = require __DIR__ . '/../../bootstrap/app.php'; diff --git a/tests/CreatesApplication.php b/tests/CreatesApplication.php index 2cd4ff0f9..dcca09975 100644 --- a/tests/CreatesApplication.php +++ b/tests/CreatesApplication.php @@ -4,19 +4,20 @@ use Hash; use Illuminate\Contracts\Console\Kernel; +use Illuminate\Foundation\Application; trait CreatesApplication { /** * Creates the application. * - * @return \Illuminate\Foundation\Application + * @return Application */ - public function createApplication() + public function createApplication(): Application { ini_set('max_execution_time', 600); - $app = require __DIR__.'/../bootstrap/app.php'; + $app = require __DIR__ . '/../bootstrap/app.php'; $app->make(Kernel::class)->bootstrap(); diff --git a/tests/DuskTestCase.php b/tests/DuskTestCase.php index 3dfbbe38e..41a4cbb91 100644 --- a/tests/DuskTestCase.php +++ b/tests/DuskTestCase.php @@ -29,7 +29,7 @@ public static function prepare(): void */ protected function driver(): RemoteWebDriver { - $options = (new ChromeOptions)->addArguments(collect([ + $options = (new ChromeOptions())->addArguments(collect([ $this->shouldStartMaximized() ? '--start-maximized' : '--window-size=1920,1080', ])->unless($this->hasHeadlessDisabled(), function (Collection $items) { return $items->merge([ @@ -41,7 +41,8 @@ protected function driver(): RemoteWebDriver return RemoteWebDriver::create( $_ENV['DUSK_DRIVER_URL'] ?? 'http://localhost:9515', DesiredCapabilities::chrome()->setCapability( - ChromeOptions::CAPABILITY, $options + ChromeOptions::CAPABILITY, + $options ) ); } diff --git a/tests/Feature/Service/AdminResetTest.php b/tests/Feature/Service/AdminResetTest.php index 507677b2c..d2fb3f463 100644 --- a/tests/Feature/Service/AdminResetTest.php +++ b/tests/Feature/Service/AdminResetTest.php @@ -24,7 +24,7 @@ public function setUp(): void $this->adminUser = factory(AdminUser::class)->create(); } - public function testResetRoute() + public function testResetRoute(): void { // Mock the Process command $mockProcess = Mockery::mock(Process::class); @@ -45,26 +45,26 @@ public function testResetRoute() ) ); // Mock DB - DOES NOT WORK. I think the Process call spawns a new thread -// $mockDb = Mockery::mock(DatabaseManager::class, ); -// $mockDb->shouldReceive('table->where->pluck') -// ->once() -// ->with("secret") -// ->andReturn([ -// [ -// 'id' => 1, -// 'userId' => 555, -// 'name' => "0", -// 'secret' => "0", -// 'provider' => "0", -// 'redirect' => "0", -// 'personal_access_client' => 3, -// 'password_client' => 4, -// 'revoked' => 5, -// 'created_at' => "0", -// 'updated_at' => "0", -// ] -// ]); -// $cls = get_class(DB::getFacadeRoot()); + // $mockDb = Mockery::mock(DatabaseManager::class, ); + // $mockDb->shouldReceive('table->where->pluck') + // ->once() + // ->with("secret") + // ->andReturn([ + // [ + // 'id' => 1, + // 'userId' => 555, + // 'name' => "0", + // 'secret' => "0", + // 'provider' => "0", + // 'redirect' => "0", + // 'personal_access_client' => 3, + // 'password_client' => 4, + // 'revoked' => 5, + // 'created_at' => "0", + // 'updated_at' => "0", + // ] + // ]); + // $cls = get_class(DB::getFacadeRoot()); // Run the test $this->actingAs($this->adminUser, 'admin') diff --git a/tests/Feature/Service/CreateMarketsPageTest.php b/tests/Feature/Service/CreateMarketsPageTest.php index 8db19fc26..7819a7d1c 100644 --- a/tests/Feature/Service/CreateMarketsPageTest.php +++ b/tests/Feature/Service/CreateMarketsPageTest.php @@ -38,8 +38,7 @@ public function setUp(): void ]; } - /** @test */ - public function testItShowsAMarketCreatePage() + public function testItShowsAMarketCreatePage(): void { $this->actingAs($this->adminUser, 'admin') ->get($this->createRoute) @@ -58,8 +57,7 @@ public function testItShowsAMarketCreatePage() ; } - /** @test */ - public function testItShowsAnErrorForBadPaymentMessage() + public function testItShowsAnErrorForBadPaymentMessage(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->createRoute) @@ -81,8 +79,8 @@ public function testItShowsAnErrorForBadPaymentMessage() ; } - /** @test */ - public function testItShowsAnErrorForBadSponsor() + + public function testItShowsAnErrorForBadSponsor(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->createRoute) @@ -106,8 +104,8 @@ public function testItShowsAnErrorForBadSponsor() ; } - /** @test */ - public function testItShowsAnErrorForBadMarket() + + public function testItShowsAnErrorForBadMarket(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->createRoute) diff --git a/tests/Feature/Service/CreateTradersPageTest.php b/tests/Feature/Service/CreateTradersPageTest.php index 1298d3930..6e9c6a8dd 100644 --- a/tests/Feature/Service/CreateTradersPageTest.php +++ b/tests/Feature/Service/CreateTradersPageTest.php @@ -45,8 +45,8 @@ public function setUp(): void ]; } - /** @test */ - public function testItShowsATraderCreatePage() + + public function testItShowsATraderCreatePage(): void { $this->actingAs($this->adminUser, 'admin') ->get($this->createRoute) @@ -67,8 +67,8 @@ public function testItShowsATraderCreatePage() ; } - /** @test */ - public function testItShowsAnErrorForBadMarket() + + public function testItShowsAnErrorForBadMarket(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->createRoute) @@ -91,8 +91,8 @@ public function testItShowsAnErrorForBadMarket() ; } - /** @test */ - public function testItShowsAnErrorForBadTraderName() + + public function testItShowsAnErrorForBadTraderName(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->createRoute) diff --git a/tests/Feature/Service/EditMarketsPageTest.php b/tests/Feature/Service/EditMarketsPageTest.php index 3f032c801..5af85d99b 100644 --- a/tests/Feature/Service/EditMarketsPageTest.php +++ b/tests/Feature/Service/EditMarketsPageTest.php @@ -47,8 +47,8 @@ public function setUp(): void ]; } - /** @test */ - public function testItShowsAMarketEditPage() + + public function testItShowsAMarketEditPage(): void { $this->actingAs($this->adminUser, 'admin') ->get($this->editRoute) @@ -67,8 +67,8 @@ public function testItShowsAMarketEditPage() ; } - /** @test */ - public function testItShowsAnErrorForBadPaymentMessage() + + public function testItShowsAnErrorForBadPaymentMessage(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->editRoute) @@ -90,8 +90,8 @@ public function testItShowsAnErrorForBadPaymentMessage() ; } - /** @test */ - public function testItShowsAnErrorForBadSponsor() + + public function testItShowsAnErrorForBadSponsor(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->editRoute) @@ -115,8 +115,8 @@ public function testItShowsAnErrorForBadSponsor() ; } - /** @test */ - public function testItShowsAnErrorForBadMarket() + + public function testItShowsAnErrorForBadMarket(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->editRoute) diff --git a/tests/Feature/Service/EditTradersPageTest.php b/tests/Feature/Service/EditTradersPageTest.php index eb5dbd127..26ec56b95 100644 --- a/tests/Feature/Service/EditTradersPageTest.php +++ b/tests/Feature/Service/EditTradersPageTest.php @@ -53,8 +53,7 @@ public function setUp(): void ]; } - /** @test */ - public function testItShowsATraderEditPage() + public function testItShowsATraderEditPage(): void { $this->actingAs($this->adminUser, 'admin') ->get($this->createRoute) @@ -77,8 +76,7 @@ public function testItShowsATraderEditPage() ; } - /** @test */ - public function testItShowsAnErrorForBadMarket() + public function testItShowsAnErrorForBadMarket(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->createRoute) @@ -100,8 +98,7 @@ public function testItShowsAnErrorForBadMarket() ; } - /** @test */ - public function testItShowsAnErrorForBadTraderName() + public function testItShowsAnErrorForBadTraderName(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->createRoute) @@ -125,8 +122,7 @@ public function testItShowsAnErrorForBadTraderName() ; } - /** @test */ - public function testItShowsAnErrorForBadDisabledCheckbox() + public function testItShowsAnErrorForBadDisabledCheckbox(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->createRoute) diff --git a/tests/Feature/Service/EditWorkerPageTest.php b/tests/Feature/Service/EditWorkerPageTest.php index ff0db58f2..6db06ff67 100644 --- a/tests/Feature/Service/EditWorkerPageTest.php +++ b/tests/Feature/Service/EditWorkerPageTest.php @@ -31,11 +31,9 @@ public function setUp(): void } /** - * @test - * * @return void */ - public function itShowsAWorkerEditPage() + public function testItShowsAWorkerEditPage(): void { // Make a CentreUser from the data with 1 homeCentre. $cu = factory(CentreUser::class)->create([ diff --git a/tests/Feature/Service/LoggingTest.php b/tests/Feature/Service/LoggingTest.php index 9824216b5..ac1e734fa 100644 --- a/tests/Feature/Service/LoggingTest.php +++ b/tests/Feature/Service/LoggingTest.php @@ -31,11 +31,9 @@ public function setUp(): void } /** - * @test - * * @return void */ - public function itLogsData() + public function testItLogsData(): void { $storage = Storage::fake('log'); @@ -49,7 +47,7 @@ public function itLogsData() $json = json_decode($response->content()); foreach (array_keys($this->postData) as $key) { - $this->assertTrue(in_array($key, $json)); + $this->assertContains($key, $json); } } } diff --git a/tests/Feature/Service/MarketsPageTest.php b/tests/Feature/Service/MarketsPageTest.php index 8720fa344..dc7faeb05 100644 --- a/tests/Feature/Service/MarketsPageTest.php +++ b/tests/Feature/Service/MarketsPageTest.php @@ -44,8 +44,8 @@ function ($m, $i) use ($sponsor) { $this->marketsRoute = route('admin.markets.index'); } - /** @test */ - public function itShowsATableWithHeaders() + + public function testItShowsATableWithHeaders(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->marketsRoute) @@ -56,8 +56,8 @@ public function itShowsATableWithHeaders() ->seeInElement('thead tr th:nth-child(4)', ''); } - /** @test */ - public function itShowsAListWithMarkets() + + public function testItShowsAListWithMarkets(): void { $sortedMarkets = $this->markets->sortBy(function ($market) { return $market->sponsor->name . '#' . @@ -71,8 +71,8 @@ public function itShowsAListWithMarkets() ->seeInElement('tbody tr:nth-child(3) td:nth-child(1)', $sortedMarkets[2]->name); } - /** @test */ - public function eachMarketHasAnAddTraderButton() + + public function testEachMarketHasAnAddTraderButton(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->marketsRoute); @@ -92,8 +92,8 @@ public function eachMarketHasAnAddTraderButton() } } - /** @test */ - public function itShowsCorrectTradersForEachMarket() + + public function testItShowsCorrectTradersForEachMarket(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->marketsRoute); @@ -118,8 +118,8 @@ public function itShowsCorrectTradersForEachMarket() } } - /** @test */ - public function itShowsAnEditButtonForEachMarket() + + public function testItShowsAnEditButtonForEachMarket(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->marketsRoute); diff --git a/tests/Feature/Service/PaymentsPageTest.php b/tests/Feature/Service/PaymentsPageTest.php index 2f32932b3..e1f824fba 100644 --- a/tests/Feature/Service/PaymentsPageTest.php +++ b/tests/Feature/Service/PaymentsPageTest.php @@ -49,8 +49,8 @@ protected function setUp(): void $vs->user_id = $user->id; $vs->save(); } - /** @test */ - public function itShowsATableWithHeaders() + + public function testItShowsATableWithHeaders(): void { $this->actingAs($this->admin_user, 'admin') ->visit($this->paymentsRoute) @@ -65,19 +65,18 @@ public function itShowsATableWithHeaders() ; } - /** @test */ - public function itShowsOutstandingPaymentsInSidebar() + + public function testItShowsOutstandingPaymentsInSidebar(): void { $this->actingAs($this->admin_user, 'admin') ->visit($this->paymentsRoute) ->assertResponseOk() - ->seeInElement('a.payments','Payment Requests') + ->seeInElement('a.payments', 'Payment Requests') ; - } - /** @test */ - public function itShowsAnErrorWhenPaymentLinkBad() + + public function testItShowsAnErrorWhenPaymentLinkBad(): void { $this->actingAs($this->admin_user, 'admin') // A poorly formed uuid should show the page, but with an error message in place of the voucher table @@ -87,8 +86,8 @@ public function itShowsAnErrorWhenPaymentLinkBad() ; } - /** @test */ - public function itShowsPaymentRequestDetailsOnlyForValidPaymentUUIDs() + + public function testItShowsPaymentRequestDetailsOnlyForValidPaymentUUIDs(): void { // A real uuid will display the data table $this->actingAs($this->admin_user, 'admin') @@ -110,8 +109,8 @@ public function itShowsPaymentRequestDetailsOnlyForValidPaymentUUIDs() ; } - /** @test */ - public function itShowsPayButtonWhenOnlyPaymentIsUnpaid() + + public function testItShowsPayButtonWhenOnlyPaymentIsUnpaid(): void { // A real unpaid uuid will display the pay button $this->actingAs($this->admin_user, 'admin') @@ -129,8 +128,8 @@ public function itShowsPayButtonWhenOnlyPaymentIsUnpaid() ; } - /** @test */ - public function itShowsTheCorrectVoucherStatus() + + public function testItShowsTheCorrectVoucherStatus(): void { // A made up uuid will display no statuses $this->actingAs($this->admin_user, 'admin') @@ -154,6 +153,4 @@ public function itShowsTheCorrectVoucherStatus() ->seeInElement('span[class="status paid"]', 'Paid') ; } - } - diff --git a/tests/Feature/Service/ServiceLiveVouchersPageTest.php b/tests/Feature/Service/ServiceLiveVouchersPageTest.php index ec1c47e0b..aa4912078 100644 --- a/tests/Feature/Service/ServiceLiveVouchersPageTest.php +++ b/tests/Feature/Service/ServiceLiveVouchersPageTest.php @@ -15,7 +15,7 @@ class ServiceLiveVouchersPageTest extends StoreTestCase { use RefreshDatabase; - public function testAdminCanViewLiveVouchers() + public function testAdminCanViewLiveVouchers(): void { $this->adminUser = factory(AdminUser::class)->create(); $this->actingAs($this->adminUser, 'admin') @@ -24,10 +24,10 @@ public function testAdminCanViewLiveVouchers() ->seeInElement('h1', 'View live vouchers') ->seeInElement('button', 'Search') ->seeInElement('a', 'Reset') - ; + ; } - public function testAdminCanSearchASingleLiveVoucher() + public function testAdminCanSearchASingleLiveVoucher(): void { $this->adminUser = factory(AdminUser::class)->create(); $this->voucherToSearch = factory(Voucher::class)->state('dispatched')->create(); @@ -40,10 +40,10 @@ public function testAdminCanSearchASingleLiveVoucher() ->press('Search') ->seeInElement('td', $this->voucherToSearch->code) ->dontSeeInElement('td', $this->otherVoucher->code) - ; + ; } - public function testAdminCannotSearchWithBadVoucherCode() + public function testAdminCannotSearchWithBadVoucherCode(): void { $this->adminUser = factory(AdminUser::class)->create(); $badSearch = ""; @@ -54,10 +54,10 @@ public function testAdminCannotSearchWithBadVoucherCode() ->type($badSearch, 'voucher_code') ->press('Search') ->seeInElement('h1', 'View live vouchers') - ; + ; } - public function testAdminCanViewHistoryOfSingleVoucher() + public function testAdminCanViewHistoryOfSingleVoucher(): void { $this->adminUser = factory(AdminUser::class)->create(); $this->voucherToSearch = factory(Voucher::class)->state('dispatched')->create(); @@ -69,10 +69,10 @@ public function testAdminCanViewHistoryOfSingleVoucher() ->seeInElement('td', $this->voucherToSearch->code) ->click('edit') ->seeInElement('h1', "Voucher Code: " . $this->voucherToSearch->code) - ; + ; } - public function testAdminCanResetSearch() + public function testAdminCanResetSearch(): void { $this->adminUser = factory(AdminUser::class)->create(); $this->voucherToSearch = factory(Voucher::class)->state('dispatched')->create(); @@ -89,6 +89,6 @@ public function testAdminCanResetSearch() ->seePageIs(route('admin.vouchers.index')) ->seeInElement('td', $this->voucherToSearch->code) ->seeInElement('td', $this->otherVoucher->code) - ; + ; } } diff --git a/tests/Feature/Service/ServiceWorkersPageTest.php b/tests/Feature/Service/ServiceWorkersPageTest.php index 02e6e2458..1aa1f4dd5 100644 --- a/tests/Feature/Service/ServiceWorkersPageTest.php +++ b/tests/Feature/Service/ServiceWorkersPageTest.php @@ -79,11 +79,9 @@ function ($c) use ($sponsor) { } /** - * @test - * * @return void */ - public function itShowsATableWithHeaders() + public function testItShowsATableWithHeaders(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->workersRoute) @@ -101,11 +99,9 @@ public function itShowsATableWithHeaders() } /** - * @test - * * @return void */ - public function itShowsAListWithUsers() + public function testItShowsAListWithUsers(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->workersRoute) @@ -119,11 +115,9 @@ public function itShowsAListWithUsers() } /** - * @test - * * @return void */ - public function itShowsADownloadWorkersListButton() + public function testItShowsADownloadWorkersListButton(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->workersRoute) diff --git a/tests/Feature/Service/SessionCookiesTest.php b/tests/Feature/Service/SessionCookiesTest.php index 6bfb8038a..bc4121239 100644 --- a/tests/Feature/Service/SessionCookiesTest.php +++ b/tests/Feature/Service/SessionCookiesTest.php @@ -13,9 +13,8 @@ class SessionCookiesTest extends TestCase /** * * @return void - * @test */ - public function testCookiesOnLogin() + public function testCookiesOnLogin(): void { $response = $this->get(route('store.login')); $cookies = $response->headers->getCookies(); @@ -34,7 +33,7 @@ public function testCookiesOnLogin() } } - public function testCookiesOnAuthenticatedUser() + public function testCookiesOnAuthenticatedUser(): void { $adminUser = factory(AdminUser::class)->create(); $response = $this diff --git a/tests/Feature/Service/TraderPaymentHistoryPageTest.php b/tests/Feature/Service/TraderPaymentHistoryPageTest.php index eb55fe347..934f6894d 100644 --- a/tests/Feature/Service/TraderPaymentHistoryPageTest.php +++ b/tests/Feature/Service/TraderPaymentHistoryPageTest.php @@ -22,7 +22,7 @@ public function setUp(): void $this->adminUser = factory(AdminUser::class)->create(); -// $this->traderHistoryRoute = route('admin.trader-payment-history.show'); + // $this->traderHistoryRoute = route('admin.trader-payment-history.show'); // Create a voucher and a trader with some info // And a user otherwise transition rightly breaks integrity constraints @@ -46,13 +46,9 @@ public function setUp(): void $vs->stateToken()->associate($stateToken); $vs->user_id = $user->id; $vs->save(); - } - /** - * @test - */ - public function itShowsATableWithHeaders() + public function testItShowsATableWithHeaders(): void { $route = route('admin.trader-payment-history.show', ['trader' => $this->trader->id]); @@ -66,4 +62,3 @@ public function itShowsATableWithHeaders() ; } } - diff --git a/tests/Feature/Service/TradersPageTest.php b/tests/Feature/Service/TradersPageTest.php index d60fff278..7bc3afe75 100644 --- a/tests/Feature/Service/TradersPageTest.php +++ b/tests/Feature/Service/TradersPageTest.php @@ -25,11 +25,9 @@ public function setUp(): void } /** - * @test - * * @return void */ - public function itShowsATableWithHeaders() + public function testItShowsATableWithHeaders(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->tradersRoute) @@ -44,15 +42,14 @@ public function itShowsATableWithHeaders() } /** - * @test * @return void */ - public function itShowsADownloadTradersListButton() + public function testItShowsADownloadTradersListButton(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->tradersRoute) ->assertResponseOk() ->seeInElement('a', 'Download Trader List') - ; + ; } } diff --git a/tests/Feature/Service/VoucherDeliveryPageTest.php b/tests/Feature/Service/VoucherDeliveryPageTest.php index 4d5a284bf..b1ece9248 100644 --- a/tests/Feature/Service/VoucherDeliveryPageTest.php +++ b/tests/Feature/Service/VoucherDeliveryPageTest.php @@ -29,11 +29,10 @@ public function setUp(): void } /** - * @test * * @return void */ - public function testItShowsAFormWithInputs() + public function testItShowsAFormWithInputs(): void { $this->actingAs($this->adminUser, 'admin') ->visit($this->voucherDeliveryRoute) diff --git a/tests/Feature/Store/DashboardPageTest.php b/tests/Feature/Store/DashboardPageTest.php index fad19dcc4..32bed1514 100644 --- a/tests/Feature/Store/DashboardPageTest.php +++ b/tests/Feature/Store/DashboardPageTest.php @@ -63,8 +63,8 @@ public function setUp(): void ]); } - /** @test */ - public function itShowsTheExportButtonsAccordingToUserRole() + + public function testItShowsTheExportButtonsAccordingToUserRole(): void { // Get FM User $fmuser = $this->fmUser; @@ -110,8 +110,8 @@ public function itShowsTheExportButtonsAccordingToUserRole() ; } - /** @test */ - public function itShowsTheLoggedInUserDetails() + + public function testItShowsTheLoggedInUserDetails(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.edit', [ 'registration' => $this->registration ])) @@ -120,8 +120,8 @@ public function itShowsTheLoggedInUserDetails() ; } - /** @test */ - public function itShowsTheExportButtonWithReleventTextForRole() + + public function testItShowsTheExportButtonWithReleventTextForRole(): void { // Get DL user $downloaduser = $this->downloadUser; @@ -134,8 +134,8 @@ public function itShowsTheExportButtonWithReleventTextForRole() } - /** @test */ - public function itShowsThePrintButtonWithReleventTextForPrintPref() + + public function testItShowsThePrintButtonWithReleventTextForPrintPref(): void { // Set centre print_pref to 'collection'. $this->centre->print_pref = config('arc.print_preferences.0'); diff --git a/tests/Feature/Store/EditPageTest.php b/tests/Feature/Store/EditPageTest.php index 0b8781972..aada00304 100644 --- a/tests/Feature/Store/EditPageTest.php +++ b/tests/Feature/Store/EditPageTest.php @@ -112,21 +112,20 @@ public function setUp(): void // The registration factory gives the family kids, so get rid of them // so we can be more specific. $this->spFamily->children()->delete(); - } - /** @test */ - public function itShowsAPrimaryCarerInput() + + public function testItShowsAPrimaryCarerInput(): void { $pri_carer = $this->registration->family->carers->first(); $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.edit', [ 'registration' => $this->registration ])) - ->seeElement('input[id="carer"][value="'. $pri_carer->name .'"]') + ->seeElement('input[id="carer"][value="' . $pri_carer->name . '"]') ; } - /** @test */ - public function itShowsASecondaryCarerInput() + + public function testItShowsASecondaryCarerInput(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.edit', [ 'registration' => $this->registration ])) @@ -135,8 +134,8 @@ public function itShowsASecondaryCarerInput() ; } - /** @test */ - public function itShowsAListOfSecondaryCarers() + + public function testItShowsAListOfSecondaryCarers(): void { // Clear the carers $this->registration->family->carers()->delete(); @@ -152,7 +151,7 @@ public function itShowsAListOfSecondaryCarers() $carers->shift(); // There should be 3... - $this->assertTrue($carers->count() == 3); + $this->assertEquals(3, $carers->count()); // Find the edit page $this->actingAs($this->centreUser, 'store') @@ -160,15 +159,15 @@ public function itShowsAListOfSecondaryCarers() ; // See the names in the page foreach ($carers as $sec_carer) { - $this->see($sec_carer->name) - ->seeElement('input[type="text"][value="'. $sec_carer->name .'"]') - ; + $this->see($sec_carer->name) + ->seeElement('input[type="text"][value="' . $sec_carer->name . '"]') + ; } } - /** @test */ - public function itShowsAChildInputComplex() + + public function testItShowsAChildInputComplex(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.edit', [ 'registration' => $this->registration ])) @@ -178,8 +177,8 @@ public function itShowsAChildInputComplex() ; } - /** @test */ - public function itShowsAListOfChildren() + + public function testItShowsAListOfChildren(): void { // Clear the children $this->registration->family->children()->delete(); @@ -192,7 +191,7 @@ public function itShowsAListOfChildren() $children = $this->registration->family->children; // There should be 4... - $this->assertTrue($children->count() == 4); + $this->assertEquals(4, $children->count()); // Find the edit page $this->actingAs($this->centreUser, 'store') @@ -200,15 +199,15 @@ public function itShowsAListOfChildren() ; // See the names in the page foreach ($children as $child) { - $this->see(''. $child->getAgeString() .'') - ->see(''. $child->getDobAsString() .'') - ->seeElement('input[type="hidden"][value="'. $child->dob->format('Y-m') .'"]') + $this->see('' . $child->getAgeString() . '') + ->see('' . $child->getDobAsString() . '') + ->seeElement('input[type="hidden"][value="' . $child->dob->format('Y-m') . '"]') ; } } - /** @test */ - public function itShowsALogoutButton() + + public function testItShowsALogoutButton(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.edit', [ 'registration' => $this->registration ])) @@ -216,8 +215,8 @@ public function itShowsALogoutButton() ; } - /** @test */ - public function itShowsAFormSaveButton() + + public function testItShowsAFormSaveButton(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.edit', [ 'registration' => $this->registration ])) @@ -225,8 +224,8 @@ public function itShowsAFormSaveButton() ; } - /** @test */ - public function itShowsAnEligibilitySelect() + + public function testItShowsAnEligibilitySelect(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) @@ -239,8 +238,8 @@ public function itShowsAnEligibilitySelect() ; } - /** @test */ - public function itShowsTheLoggedInUserDetails() + + public function testItShowsTheLoggedInUserDetails(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.edit', [ 'registration' => $this->registration->id ])) @@ -249,8 +248,8 @@ public function itShowsTheLoggedInUserDetails() ; } - /** @test */ - public function itShowsTheLeavingFormIfFamilyIsOnScheme() + + public function testItShowsTheLeavingFormIfFamilyIsOnScheme(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.edit', [ 'registration' => $this->registration->id ])) @@ -258,8 +257,8 @@ public function itShowsTheLeavingFormIfFamilyIsOnScheme() ; } - /** @test */ - public function itDoesNotShowTheLeavingFormIfFamilyHasLeftScheme() + + public function testItDoesNotShowTheLeavingFormIfFamilyHasLeftScheme(): void { $family = $this->registration->family; $family->leaving_on = Carbon::now(); @@ -271,8 +270,8 @@ public function itDoesNotShowTheLeavingFormIfFamilyHasLeftScheme() ; } - /** @test */ - public function childrensDOBsGiveExpectedAge() + + public function testChildrensDOBsGiveExpectedAge(): void { // Set Carbon::now to 01/01/2018 Carbon::setTestNow(Carbon::parse('first day of January 2018')->startOfDay()); @@ -324,8 +323,8 @@ public function childrensDOBsGiveExpectedAge() Carbon::setTestNow(); } - /** @test */ - public function itWillNotAcceptAnInvalidLeavingReason() + + public function testItWillNotAcceptAnInvalidLeavingReason(): void { $response = $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.edit', $this->registration->id)) @@ -340,8 +339,8 @@ public function itWillNotAcceptAnInvalidLeavingReason() $this->assertEquals('The selected leaving reason is invalid.', $response->exception->getMessage()); } - /** @test */ - public function itWillRejectUpdatesIfFamilyHasLeft() + + public function testItWillRejectUpdatesIfFamilyHasLeft(): void { $family = $this->registration->family; $family->leaving_on = Carbon::now(); @@ -366,8 +365,8 @@ public function itWillRejectUpdatesIfFamilyHasLeft() $this->assertResponseStatus(403); } - /** @test */ - public function itWillRejectLeavingWithoutAReason() + + public function testItWillRejectLeavingWithoutAReason(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.edit', $this->registration->id)) @@ -380,8 +379,8 @@ public function itWillRejectLeavingWithoutAReason() ; } - /** @test */ - public function itWillAcceptLeavingWithAReason() + + public function testItWillAcceptLeavingWithAReason(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.edit', $this->registration->id)) @@ -392,8 +391,8 @@ public function itWillAcceptLeavingWithAReason() ; } - /** @test */ - public function leaveAmountIncreases() + + public function testLeaveAmountIncreases(): void { // Create a family $family = factory(Family::class)->create(); @@ -416,8 +415,8 @@ public function leaveAmountIncreases() ]); } - /** @test */ - public function itShowsTheCorrectEvaluatingRules() + + public function testItShowsTheCorrectEvaluatingRules(): void { $evals = $this->registration ->getEvaluator() @@ -446,7 +445,7 @@ public function itShowsTheCorrectEvaluatingRules() ->visit(URL::route('store.registration.edit', $this->registration->id)); // See it has increased by one - $this->assertCount($startingEvalCount+1, $this->crawler->filter('ul#creditables li')); + $this->assertCount($startingEvalCount + 1, $this->crawler->filter('ul#creditables li')); // See the reason $rule = new ChildIsPrimarySchoolAge(); $this->see($rule->reason); @@ -477,58 +476,58 @@ public function itShowsTheCorrectEvaluatingRules() $this->see($rule->reason); } - /** @test */ - public function ICanSeeAScottishChildCanBeDeferred() + + public function testICanSeeAScottishChildCanBeDeferred(): void { - Config::set('arc.scottish_school_month', Carbon::now()->month + 1); - $canDefer = factory(Child::class)->state('canDefer')->make(); - $this->scottishFamily->children()->save($canDefer); - $inputID = "children[" . $canDefer->id . "][deferred]"; - $selector = 'input[id=\'' . $inputID . '\']'; - $this->actingAs($this->scottishCentreUser, 'store') - ->visit(URL::route('store.registration.edit', $this->scottishRegistration->id)) - ->see(''. $canDefer->getAgeString() .'') - ->see(''. $canDefer->getDobAsString() .'') - ->seeElement('input[type="hidden"][value="'. $canDefer->dob->format('Y-m') .'"]') - ->seeElement($selector) - ; + Config::set('arc.scottish_school_month', Carbon::now()->month + 1); + $canDefer = factory(Child::class)->state('canDefer')->make(); + $this->scottishFamily->children()->save($canDefer); + $inputID = "children[" . $canDefer->id . "][deferred]"; + $selector = 'input[id=\'' . $inputID . '\']'; + $this->actingAs($this->scottishCentreUser, 'store') + ->visit(URL::route('store.registration.edit', $this->scottishRegistration->id)) + ->see('' . $canDefer->getAgeString() . '') + ->see('' . $canDefer->getDobAsString() . '') + ->seeElement('input[type="hidden"][value="' . $canDefer->dob->format('Y-m') . '"]') + ->seeElement($selector) + ; } - /** @test */ - public function ICanDeferAScottishChild() + + public function testICanDeferAScottishChild(): void { - Config::set('arc.scottish_school_month', Carbon::now()->month + 1); - $canDefer = factory(Child::class)->state('canDefer')->make(); - $this->scottishFamily->children()->save($canDefer); - $this->seeInDatabase('children', [ - 'id' => $canDefer->id, - 'deferred' => 0 - ]); - - // This is what happens when you have square brackets in ids. - $inputID = "children[" . $canDefer->id . "][deferred]"; - $selector = 'input[id=\'' . $inputID . '\']'; - - $this->actingAs($this->scottishCentreUser, 'store') - ->visit(URL::route('store.registration.edit', $this->scottishRegistration->id)) - ->see(''. $canDefer->getAgeString() .'') - ->see(''. $canDefer->getDobAsString() .'') - ->seeElement('input[type="hidden"][value="'. $canDefer->dob->format('Y-m') .'"]') - ->seeElement($selector) - ->check($inputID) - ->press('Save Changes') - ->seePageIs(URL::route('store.registration.edit', [ 'registration' => $this->scottishRegistration->id ])) - ; - // Saving changes deletes the children and re-adds them, - // so we can't use the same id. Since we only made one kid, - // we'll need to trust that this check is fine. - $this->seeInDatabase('children', [ - 'deferred' => 1 - ]); + Config::set('arc.scottish_school_month', Carbon::now()->month + 1); + $canDefer = factory(Child::class)->state('canDefer')->make(); + $this->scottishFamily->children()->save($canDefer); + $this->seeInDatabase('children', [ + 'id' => $canDefer->id, + 'deferred' => 0 + ]); + + // This is what happens when you have square brackets in ids. + $inputID = "children[" . $canDefer->id . "][deferred]"; + $selector = 'input[id=\'' . $inputID . '\']'; + + $this->actingAs($this->scottishCentreUser, 'store') + ->visit(URL::route('store.registration.edit', $this->scottishRegistration->id)) + ->see('' . $canDefer->getAgeString() . '') + ->see('' . $canDefer->getDobAsString() . '') + ->seeElement('input[type="hidden"][value="' . $canDefer->dob->format('Y-m') . '"]') + ->seeElement($selector) + ->check($inputID) + ->press('Save Changes') + ->seePageIs(URL::route('store.registration.edit', [ 'registration' => $this->scottishRegistration->id ])) + ; + // Saving changes deletes the children and re-adds them, + // so we can't use the same id. Since we only made one kid, + // we'll need to trust that this check is fine. + $this->seeInDatabase('children', [ + 'deferred' => 1 + ]); } - /** @test */ - public function itShowsAnSPRegistrationsDetailsCorrectly() + + public function testItShowsAnSPRegistrationsDetailsCorrectly(): void { $new_carers = factory(Carer::class, 2)->make(); $this->spRegistration->family->carers()->saveMany($new_carers); @@ -537,7 +536,7 @@ public function itShowsAnSPRegistrationsDetailsCorrectly() $this->spRegistration->family->children()->saveMany($new_participants); // $children = $this->spRegistration->family->children; - $this->assertTrue($children->count() === 3); + $this->assertSame(3, $children->count()); // Find the edit page $this->actingAs($this->spCentreUser, 'store') ->visit(URL::route('store.registration.edit', [ 'registration' => $this->spRegistration ])) @@ -548,15 +547,15 @@ public function itShowsAnSPRegistrationsDetailsCorrectly() } foreach ($new_participants as $new_participant) { - $this->see(''. explode(',',$new_participant->getAgeString())[0] .''); + $this->see('' . explode(',', $new_participant->getAgeString())[0] . ''); $this->dontSee('ID Checked'); $this->dontSee('eligibility-hsbs'); $this->dontSee('eligibility-nrpf'); } } - /** @test */ - public function ICanDeleteASecondaryCarerWhoHasCollectedABundle() + + public function testICanDeleteASecondaryCarerWhoHasCollectedABundle(): void { $this->registration->family->carers()->delete(); $main_carer = factory(Carer::class)->make([ diff --git a/tests/Feature/Store/ForgotPasswordPageTest.php b/tests/Feature/Store/ForgotPasswordPageTest.php index 0374f9af1..2c955142f 100644 --- a/tests/Feature/Store/ForgotPasswordPageTest.php +++ b/tests/Feature/Store/ForgotPasswordPageTest.php @@ -1,4 +1,5 @@ centreUser->centres()->attach($this->centre->id, ['homeCentre' => true]); } - /** @test */ - public function itHasAnEmailInput() + + public function testItHasAnEmailInput(): void { $this->visit(route('store.password.request')) ->seeElement('input[type=email][name=email]') ; } - /** @test */ - public function itHasASubmitButton() + + public function testItHasASubmitButton(): void { $this->visit(route('store.password.request')) ->seeElement('button[type=submit]') @@ -52,8 +53,8 @@ public function itHasASubmitButton() ; } - /** @test */ - public function itResetsPasswordForUserByEmailResetLink() + + public function testItResetsPasswordForUserByEmailResetLink(): void { Notification::fake(); @@ -74,8 +75,8 @@ function ($notification, $channels) use ($user) { ); } - /** @test */ - public function itCannotEffectRedirectWithAManipulatedRefererHeader() + + public function testItCannotEffectRedirectWithAManipulatedRefererHeader(): void { Mail::fake(); diff --git a/tests/Feature/Store/HistoryPageTest.php b/tests/Feature/Store/HistoryPageTest.php index 00a4f1e94..3b2cff125 100644 --- a/tests/Feature/Store/HistoryPageTest.php +++ b/tests/Feature/Store/HistoryPageTest.php @@ -42,8 +42,7 @@ public function setUp(): void ]); } - /** @test **/ - public function itShowsAlertWhenRegistrationHasNoBundlesAssigned() + public function testItShowsAlertWhenRegistrationHasNoBundlesAssigned(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.collection-history', [ 'registration' => $this->registration ])) diff --git a/tests/Feature/Store/LoginPageTest.php b/tests/Feature/Store/LoginPageTest.php index 7e6b0e3d2..6cfdef06e 100644 --- a/tests/Feature/Store/LoginPageTest.php +++ b/tests/Feature/Store/LoginPageTest.php @@ -1,4 +1,5 @@ visit(URL::route('store.login')) ->assertResponseStatus(200) @@ -48,8 +48,8 @@ public function itShowsALoginPageWhenRouted() ; } - /** @test */ - public function itDoesNotShowTheLoggedInUserDetails() + + public function testItDoesNotShowTheLoggedInUserDetails(): void { $this->visit(URL::route('store.login')) ->dontSee($this->centreUser->name) @@ -57,40 +57,40 @@ public function itDoesNotShowTheLoggedInUserDetails() ; } - /** @test */ - public function itShowsAForgotPasswordLink() + + public function testItShowsAForgotPasswordLink(): void { $this->visit(URL::route('store.login')) - ->see('href="'. route('store.password.request') .'"') + ->see('href="' . route('store.password.request') . '"') ; } - /** @test */ - public function itShowsAUsernameInputBox() + + public function testItShowsAUsernameInputBox(): void { $this->visit(URL::route('store.login')) ->seeElement('input[id=email]') ; } - /** @test */ - public function itShowsAPasswordInputBox() + + public function testItShowsAPasswordInputBox(): void { $this->visit(URL::route('store.login')) ->seeElement('input[id=password]') ; } - /** @test */ - public function itDoesNotShowTheAuthUserMastheadWithLogoutLink() + + public function testItDoesNotShowTheAuthUserMastheadWithLogoutLink(): void { $this->visit(URL::route('store.login')) - ->dontSee('href="'. route('store.login') .'"') + ->dontSee('href="' . route('store.login') . '"') ; } - /** @test */ - public function itAllowsAValidUserToLogin() + + public function testItAllowsAValidUserToLogin(): void { $this->visit(URL::route('store.login')) ->type('testuser@example.com', 'email') @@ -100,8 +100,8 @@ public function itAllowsAValidUserToLogin() ; } - /** @test */ - public function itForbidsAnInvalidUserToLogin() + + public function testItForbidsAnInvalidUserToLogin(): void { $this->visit(URL::route('store.login')) ->type('notauser@example.com', 'email') @@ -112,8 +112,8 @@ public function itForbidsAnInvalidUserToLogin() ; } - /** @test */ - public function itForbidsADeletedUserToLogin() + + public function testItForbidsADeletedUserToLogin(): void { $this->visit(URL::route('store.login')) ->type($this->deletedcu->email, 'email') @@ -124,8 +124,8 @@ public function itForbidsADeletedUserToLogin() ; } - /** @test */ - public function itRequiresAPasswordToLogin() + + public function testItRequiresAPasswordToLogin(): void { $this->visit(URL::route('store.login')) ->type('testuser@example.com', 'email') @@ -135,23 +135,22 @@ public function itRequiresAPasswordToLogin() ; } - /** @test */ - public function itRequiresAnEmailToLogin() + + public function testItRequiresAnEmailToLogin(): void { $this->visit(URL::route('store.login')) ->type('test_user_pass', 'password') ->press('Log In') ->seePageIs(URL::route('store.login')) ->see(trans('validation.required', ['attribute' => "email"])); - ; } - public function itShowsACookieWarning() + public function testItShowsACookieNotice(): void { $this->visit(URL::route('store.login')) - ->see('cookie.agree') + ->see('cookie-agree') ->see(config('arc.links.privacy_policy')) - ->see('cookie-warning') - ; + ->see('cookie-notice') + ; } } diff --git a/tests/Feature/Store/RegistrationPageTest.php b/tests/Feature/Store/RegistrationPageTest.php index 5a738c359..6a70033c6 100644 --- a/tests/Feature/Store/RegistrationPageTest.php +++ b/tests/Feature/Store/RegistrationPageTest.php @@ -59,8 +59,8 @@ public function setUp(): void $this->spCentreUser->centres()->attach($this->spCentre->id, ['homeCentre' => true]); } - /** @test */ - public function itShowsAPrimaryCarerInput() + + public function testItShowsAPrimaryCarerInput(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) @@ -68,8 +68,8 @@ public function itShowsAPrimaryCarerInput() ; } - /** @test */ - public function itShowsASecondaryCarerInput() + + public function testItShowsASecondaryCarerInput(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) @@ -78,8 +78,8 @@ public function itShowsASecondaryCarerInput() ; } - /** @test */ - public function itShowsAChildInputComplex() + + public function testItShowsAChildInputComplex(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) @@ -89,8 +89,8 @@ public function itShowsAChildInputComplex() ; } - /** @test */ - public function itShowsAConsentCheckbox() + + public function testItShowsAConsentCheckbox(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) @@ -98,8 +98,8 @@ public function itShowsAConsentCheckbox() ; } - /** @test */ - public function itShowsAnEligibilitySelect() + + public function testItShowsAnEligibilitySelect(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) @@ -112,8 +112,8 @@ public function itShowsAnEligibilitySelect() ; } - /** @test */ - public function itShowsAFormSaveButton() + + public function testItShowsAFormSaveButton(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) @@ -121,8 +121,8 @@ public function itShowsAFormSaveButton() ; } - /** @test */ - public function itShowsALogoutButton() + + public function testItShowsALogoutButton(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) @@ -130,8 +130,8 @@ public function itShowsALogoutButton() ; } - /** @test */ - public function itShowsTheLoggedInUserDetails() + + public function testItShowsTheLoggedInUserDetails(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) @@ -140,10 +140,7 @@ public function itShowsTheLoggedInUserDetails() ; } - /** - * @test - */ - public function logoDoesntRedirectMeToDashboard() + public function testLogoDoesntRedirectMeToDashboard(): void { $this->expectException(InvalidArgumentException::class); // Create some centres @@ -157,8 +154,8 @@ public function logoDoesntRedirectMeToDashboard() ->seePageIs(URL::route('store.registration.create')); } - /** @test */ - public function itCanSaveARegistration() + + public function testItCanSaveARegistration(): void { // There are no registrations $this->assertEquals(0, Registration::get()->count()); @@ -186,8 +183,8 @@ public function itCanSaveARegistration() $this->assertEquals('Test Carer', $registration->family->carers->first()->name); } - /** @test */ - public function itRequiresConsentToSave() + + public function testItRequiresConsentToSave(): void { // There are no registrations $this->assertEquals(0, Registration::get()->count()); @@ -205,8 +202,8 @@ public function itRequiresConsentToSave() $this->assertEquals(0, Registration::get()->count()); } - /** @test */ - public function itRequiresAPrimaryCarerToSave() + + public function testItRequiresAPrimaryCarerToSave(): void { // There are no registrations $this->assertEquals(0, Registration::get()->count()); @@ -224,8 +221,8 @@ public function itRequiresAPrimaryCarerToSave() $this->assertEquals(0, Registration::get()->count()); } - /** @test */ - public function selectingReceivingHSPutsDateInTable() + + public function testSelectingReceivingHSPutsDateInTable(): void { $this->assertEquals(0, Registration::count()); $this->actingAs($this->centreUser, 'store') @@ -242,8 +239,8 @@ public function selectingReceivingHSPutsDateInTable() $this->assertNotNull($registration->eligible_from); } - /** @test */ - public function selectingNotReceivingHSPutsNullInTable() + + public function testSelectingNotReceivingHSPutsNullInTable(): void { $this->assertEquals(0, Registration::get()->count()); $this->actingAs($this->centreUser, 'store') @@ -260,8 +257,8 @@ public function selectingNotReceivingHSPutsNullInTable() $this->assertNull($registration->eligible_from); } - /** @test */ - public function changingToNotReceivingHSPutsNullInTable() + + public function testChangingToNotReceivingHSPutsNullInTable(): void { $this->assertEquals(0, Registration::get()->count()); $this->actingAs($this->centreUser, 'store') @@ -287,8 +284,8 @@ public function changingToNotReceivingHSPutsNullInTable() $this->assertNull($registration->eligible_from); } - /** @test */ - public function updatingOtherFieldsDoesNotChangeEligibiltyDate() + + public function testUpdatingOtherFieldsDoesNotChangeEligibiltyDate(): void { $this->assertEquals(0, Registration::get()->count()); $this->actingAs($this->centreUser, 'store') @@ -316,8 +313,8 @@ public function updatingOtherFieldsDoesNotChangeEligibiltyDate() } } - /** @test */ - public function asAnSPUserICanSeeTheCorrectInputs() + + public function testAsAnSPUserICanSeeTheCorrectInputs(): void { $this->actingAs($this->spCentreUser, 'store') ->visit(URL::route('store.registration.create')) diff --git a/tests/Feature/Store/RejoinPageTest.php b/tests/Feature/Store/RejoinPageTest.php index 967732c32..5fafe3d96 100644 --- a/tests/Feature/Store/RejoinPageTest.php +++ b/tests/Feature/Store/RejoinPageTest.php @@ -58,8 +58,8 @@ public function setUp(): void ]); } - /** @test */ - public function itShowsTheCorrectViewFields() + + public function testItShowsTheCorrectViewFields(): void { $pri_carer = $this->registration->family->carers->first(); $this->actingAs($this->centreUser, 'store') @@ -69,23 +69,23 @@ public function itShowsTheCorrectViewFields() ; } - /** @test */ - public function itDoesNotShowTheChildrenNames() + + public function testItDoesNotShowTheChildrenNames(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.view', [ 'registration' => $this->registration ])) ; // Don't see the names of children in the page foreach ($this->children as $child) { - $this->dontSee(''. $child->getAgeString() .'') - ->dontSee(''. $child->getDobAsString() .'') + $this->dontSee('' . $child->getAgeString() . '') + ->dontSee('' . $child->getDobAsString() . '') ->dontSeeInElement('input[type="hidden"]', $child->dob->format('Y-m')) ; } } - /** @test */ - public function itShowsARejoinButton() + + public function testItShowsARejoinButton(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.view', [ 'registration' => $this->registration ])) @@ -93,8 +93,8 @@ public function itShowsARejoinButton() ; } - /** @test */ - public function itDoesNotShowRemoveThisFamilyButton() + + public function testItDoesNotShowRemoveThisFamilyButton(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.view', [ 'registration' => $this->registration ])) @@ -102,8 +102,8 @@ public function itDoesNotShowRemoveThisFamilyButton() ; } - /** @test */ - public function itDoesNotShowASecondaryCarerInput() + + public function testItDoesNotShowASecondaryCarerInput(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.view', [ 'registration' => $this->registration ])) @@ -112,8 +112,8 @@ public function itDoesNotShowASecondaryCarerInput() ; } - /** @test */ - public function itWillAllowFamilyToRejoin() + + public function testItWillAllowFamilyToRejoin(): void { $knownDate = Carbon::create(2023, 1, 13, 12); Carbon::setTestNow($knownDate); diff --git a/tests/Feature/Store/SearchPageTest.php b/tests/Feature/Store/SearchPageTest.php index 2e102976e..e64f1bd8d 100644 --- a/tests/Feature/Store/SearchPageTest.php +++ b/tests/Feature/Store/SearchPageTest.php @@ -1,4 +1,5 @@ create(); @@ -34,8 +35,8 @@ public function itShowsTheLoggedInUser(): void ; } - /** @test */ - public function itShowsRegistrationsFromNeighbourCentres(): void + + public function testItShowsRegistrationsFromNeighbourCentres(): void { // Create a single Sponsor $sponsor = factory(Sponsor::class)->create(); @@ -80,8 +81,8 @@ public function itShowsRegistrationsFromNeighbourCentres(): void } - /** @test */ - public function itShowsRegistrationsFromMyCentre(): void + + public function testItShowsRegistrationsFromMyCentre(): void { // Create a single Sponsor $sponsor = factory(Sponsor::class)->create(); @@ -114,8 +115,7 @@ public function itShowsRegistrationsFromMyCentre(): void } } - /** test */ - public function itDoesNotShowRegistrationsFromUnrelatedCentres(): void + public function testItDoesNotShowRegistrationsFromUnrelatedCentres(): void { // Create a single Sponsor $sponsor = factory(Sponsor::class)->create(); @@ -125,7 +125,7 @@ public function itDoesNotShowRegistrationsFromUnrelatedCentres(): void "sponsor_id" => $sponsor->id, ]); - $alien_centre = factory(Centre::class, 2)->create([ + $alien_centre = factory(Centre::class)->create([ "sponsor_id" => factory(Sponsor::class)->create()->id, ]); @@ -160,13 +160,13 @@ public function itDoesNotShowRegistrationsFromUnrelatedCentres(): void // Check we can see the edit link with the registration ID in it. foreach ($registrations3 as $registration) { - $edit_url_string = URL::route('store.registration.edit', [ 'id' => $registration->id]); + $edit_url_string = URL::route('store.registration.edit', [ 'registration' => $registration->id]); $this->dontSee($edit_url_string); } } - /** @test */ - public function itShowsThePrimaryCarerName(): void + + public function testItShowsThePrimaryCarerName(): void { // Create a Centre (and, implicitly a random Sponsor) @@ -194,8 +194,8 @@ public function itShowsThePrimaryCarerName(): void ->see($pri_carer->name); } - /** @test */ - public function itShowsTheRVID(): void + + public function testItShowsTheRVID(): void { // Create a Centre $centre = factory(Centre::class)->create(); @@ -220,8 +220,8 @@ public function itShowsTheRVID(): void } - /** @test */ - public function itShowsFamilyPrimaryCarersAlphabetically(): void + + public function testItShowsFamilyPrimaryCarersAlphabetically(): void { // Create a Centre (and, implicitly a random Sponsor) $centre = factory(Centre::class)->create(); @@ -259,8 +259,8 @@ public function itShowsFamilyPrimaryCarersAlphabetically(): void } } - /** @test */ - public function itHasTheExpectedResultsPerPage(): void + + public function testItHasTheExpectedResultsPerPage(): void { $centre = factory(Centre::class)->create(); @@ -287,8 +287,8 @@ public function itHasTheExpectedResultsPerPage(): void $this->assertCount(10, $this->crawler->filter($selector)); } - /** @test */ - public function itShowsCentreLabelsForUsersByDefault(): void + + public function testItShowsCentreLabelsForUsersByDefault(): void { // Create some centres $centre1 = factory(Centre::class)->create([ @@ -331,8 +331,8 @@ public function itShowsCentreLabelsForUsersByDefault(): void $this->assertCount(9, $this->crawler->filter('div.secondary_info')); } - /** @test */ - public function itDoesNotShowLeftFamiliesByDefault(): void + + public function testItDoesNotShowLeftFamiliesByDefault(): void { $centre = factory(Centre::class)->create(); @@ -360,8 +360,8 @@ public function itDoesNotShowLeftFamiliesByDefault(): void ->dontSee($leavingFamily->carers->first()); } - /** @test */ - public function itShowsLeftFamilyRegistrationsAsDistinct(): void + + public function testItShowsLeftFamilyRegistrationsAsDistinct(): void { $this->markTestSkipped('Waiting for Dusk'); $centre = factory(Centre::class)->create(); @@ -393,8 +393,8 @@ public function itShowsLeftFamilyRegistrationsAsDistinct(): void $this->assertCount(9, $this->crawler->filter('tr.active')); } - /** @test */ - public function itPreventsAccessToLeftFamilyRegistrations(): void + + public function testItPreventsAccessToLeftFamilyRegistrations(): void { $this->markTestSkipped('Waiting for Dusk'); $centre = factory(Centre::class)->create(); @@ -429,8 +429,8 @@ public function itPreventsAccessToLeftFamilyRegistrations(): void $this->assertCount(18, $this->crawler->filter('tr.active td.right.no-wrap div:not(.disabled)')); } - /** @test */ - public function aVouchersButtonIsPresent(): void + + public function testAVouchersButtonIsPresent(): void { // Create a Centre $centre = factory(Centre::class)->create(); diff --git a/tests/Feature/Store/SessionCookiesTest.php b/tests/Feature/Store/SessionCookiesTest.php index 27d33e988..060e589fd 100644 --- a/tests/Feature/Store/SessionCookiesTest.php +++ b/tests/Feature/Store/SessionCookiesTest.php @@ -14,9 +14,8 @@ class SessionCookiesTest extends TestCase /** * * @return void - * @test */ - public function testCookiesOnLogin() + public function testCookiesOnLogin(): void { $response = $this->get(route('store.login')); $cookies = $response->headers->getCookies(); @@ -35,7 +34,7 @@ public function testCookiesOnLogin() } } - public function testCookiesOnAuthenticatedUser() + public function testCookiesOnAuthenticatedUser(): void { $centre = factory(Centre::class)->create(); $centreUser = factory(CentreUser::class)->create([ diff --git a/tests/Feature/Store/VoucherManagerTest.php b/tests/Feature/Store/VoucherManagerTest.php index 671b5190e..3b2e0efea 100644 --- a/tests/Feature/Store/VoucherManagerTest.php +++ b/tests/Feature/Store/VoucherManagerTest.php @@ -1,4 +1,5 @@ seeElement("#allocate-vouchers"); } - /** @test */ - public function testRVIDVisible() + + public function testRVIDVisible(): void { // Check we can see "Their RV-ID is" on this page // Check it's the right RV-ID for the family/household @@ -100,8 +101,8 @@ public function testRVIDVisible() ->see($rvid); } - /** @test */ - public function itCanShowFamilyWarnings() + + public function testItCanShowFamilyWarnings(): void { /** * Create @@ -171,8 +172,8 @@ public function itCanShowFamilyWarnings() } } - /** @test */ - public function testFollowLinks() + + public function testFollowLinks(): void { // Check edit family link @@ -194,8 +195,8 @@ public function testFollowLinks() ->assertResponseOk(); } - /** @test */ - public function testAddBulkVoucher() + + public function testAddBulkVoucher(): void { // check we can bulk add vouchers $this->actingAs($this->fmUser, 'store') @@ -209,16 +210,16 @@ public function testAddBulkVoucher() ->see('TST10001') ; // Three vouchers and the delete all button. - $this->assertEquals(4, count($this->crawler->filter('.delete-button'))); + $this->assertCount(4, $this->crawler->filter('.delete-button')); // check we can remove a bundle of vouchers $this->press('delete-all-button'); // The delete all button is hidden - $this->assertEquals(1, count($this->crawler->filter('.delete-button'))); + $this->assertCount(1, $this->crawler->filter('.delete-button')); } - /** @test */ - public function testAddSingleVoucher() + + public function testAddSingleVoucher(): void { // check we can add a single voucher $this->actingAs($this->fmUser, 'store') @@ -229,11 +230,11 @@ public function testAddSingleVoucher() ->see('TST09999') ; // One voucher and the delete all button. - $this->assertEquals(2, count($this->crawler->filter('.delete-button'))); + $this->assertCount(2, $this->crawler->filter('.delete-button')); // check we can remove a voucher $this->press('delete-button'); // The delete all button is hidden - $this->assertEquals(1, count($this->crawler->filter('.delete-button'))); + $this->assertCount(1, $this->crawler->filter('.delete-button')); } } diff --git a/tests/MysqlStoreTestCase.php b/tests/MysqlStoreTestCase.php index 4e905a494..1b03f54cd 100644 --- a/tests/MysqlStoreTestCase.php +++ b/tests/MysqlStoreTestCase.php @@ -1,4 +1,5 @@ crawler->filter($selector)->eq($pos)->text()); $this->assertStringContainsString($text, $element_text); diff --git a/tests/Unit/Controllers/Api/ApiVoucherControllerTest.php b/tests/Unit/Controllers/Api/ApiVoucherControllerTest.php index 202282e35..f9e3e03f0 100644 --- a/tests/Unit/Controllers/Api/ApiVoucherControllerTest.php +++ b/tests/Unit/Controllers/Api/ApiVoucherControllerTest.php @@ -43,10 +43,10 @@ public function setUp(): void * Transition to delivery * * @param $vouchers - * @param Centre|null $centre + * @param Centre $centre * @param Carbon|null $deliveryDate */ - private function dispatchVouchers($vouchers, Centre $centre, Carbon $deliveryDate = null) + private function dispatchVouchers($vouchers, Centre $centre, Carbon $deliveryDate = null): void { $deliveryDate = $deliveryDate ?? Carbon::today(); @@ -64,8 +64,8 @@ private function dispatchVouchers($vouchers, Centre $centre, Carbon $deliveryDat }); } - /** @test */ - public function testItNeverTidiesOldTokensOnConfirmTransitions() + + public function testItNeverTidiesOldTokensOnConfirmTransitions(): void { Mail::fake(); @@ -145,8 +145,8 @@ public function testItNeverTidiesOldTokensOnConfirmTransitions() $this->assertEquals(2, StateToken::all()->count()); } - /** @test */ - public function testItAttachesTokensToPaymentPendingStates() + + public function testItAttachesTokensToPaymentPendingStates(): void { Mail::fake(); @@ -193,13 +193,13 @@ public function testItAttachesTokensToPaymentPendingStates() $this->vouchers ->each(function ($voucher) use ($stateToken) { $voucherState = $voucher->getPriorState(); - $this->assertEquals($voucherState->to, 'payment_pending'); + $this->assertEquals('payment_pending', $voucherState->to); $this->assertEquals($voucherState->stateToken->id, $stateToken->id); }); } - /** @test */ - public function testItReturnsArrayOfUndeliveredVouchers() + + public function testItReturnsArrayOfUndeliveredVouchers(): void { // Create a Centre $centre = factory(Centre::class)->create(); @@ -221,7 +221,7 @@ public function testItReturnsArrayOfUndeliveredVouchers() $expectedCounts = [ // The last one is undelivered. - 'success_amount' => $this->vouchers->count()-1, + 'success_amount' => $this->vouchers->count() - 1, 'duplicate_amount' => 0, // The invalid one is the one hat was undelivered. 'invalid_amount' => 1, diff --git a/tests/Unit/Controllers/Api/TraderControllerTest.php b/tests/Unit/Controllers/Api/TraderControllerTest.php index f9c46ad22..176d89197 100644 --- a/tests/Unit/Controllers/Api/TraderControllerTest.php +++ b/tests/Unit/Controllers/Api/TraderControllerTest.php @@ -82,7 +82,7 @@ protected function setUp(): void * * Asserts that the correct JSON structure is returned along with the correct market data. */ - public function testTradersControllerIndex() + public function testTradersControllerIndex(): void { $trader = factory(Trader::class)->create( [ @@ -117,9 +117,9 @@ public function testTradersControllerIndex() ]); } - public function testShowVoucherHistoryCompilesListOfPaymentHistory() + public function testShowVoucherHistoryCompilesListOfPaymentHistory(): void { - $traderController = new TraderController; + $traderController = new TraderController(); $data = json_decode( $traderController->showVoucherHistory($this->traders[0])->getContent(), false @@ -139,7 +139,7 @@ public function testShowVoucherHistoryCompilesListOfPaymentHistory() $this->assertEquals($data[0]->vouchers[2]->reimbursed_on, $today); } - public function testItLimitsTheVoucherHistoryTo15() + public function testItLimitsTheVoucherHistoryTo15(): void { $date = Carbon::now()->subMonths(3); @@ -163,7 +163,7 @@ public function testItLimitsTheVoucherHistoryTo15() }); // fire up the controller and ask for some things. - $traderController = new TraderController; + $traderController = new TraderController(); $response = $traderController->showVoucherHistory($this->traders[0]); $data = json_decode( $response->getContent(), @@ -173,7 +173,7 @@ public function testItLimitsTheVoucherHistoryTo15() $this->assertCount(15, $data); } - public function testItPutsPaginationInTheVoucherHistory() + public function testItPutsPaginationInTheVoucherHistory(): void { $date = Carbon::now()->subMonths(3); @@ -197,7 +197,7 @@ public function testItPutsPaginationInTheVoucherHistory() }); // fire up the controller and ask for some things. - $traderController = new TraderController; + $traderController = new TraderController(); $response = $traderController->showVoucherHistory($this->traders[0]); @@ -230,7 +230,7 @@ public function testItPutsPaginationInTheVoucherHistory() /** * Tests the email all voucher history API response. */ - public function testEmailVoucherHistoryAllDates() + public function testEmailVoucherHistoryAllDates(): void { Mail::fake(); @@ -252,7 +252,7 @@ public function testEmailVoucherHistoryAllDates() /** * Tests the email specific date voucher history API response. */ - public function testEmailVoucherHistorySpecificDate() + public function testEmailVoucherHistorySpecificDate(): void { Mail::fake(); @@ -281,7 +281,7 @@ public function testEmailVoucherHistorySpecificDate() /** * Tests the voucher history not emailed to user not auth'd for trader. */ - public function testEmailVoucherHistoryToNonAuthdUser() + public function testEmailVoucherHistoryToNonAuthdUser(): void { $this->actingAs($this->user, 'api') ->json('POST', route('api.trader.voucher-history-email', 1), [ @@ -295,11 +295,10 @@ public function testEmailVoucherHistoryToNonAuthdUser() * @param string $header * @return array */ - private function linkHeaderToArray(string $header) + private function linkHeaderToArray(string $header): array { $values = []; - foreach (explode(',', $header) as $link) - { + foreach (explode(',', $header) as $link) { $values = array_merge($values, $this->extractLinkData($link)); } return $values; @@ -309,7 +308,7 @@ private function linkHeaderToArray(string $header) * @param string $linkHeader * @return array[] */ - private function extractLinkData(string $linkHeader) + private function extractLinkData(string $linkHeader): array { preg_match('/<(.*?(?:(?:\?|\&)page=(\d+).*)?)>.*rel="(.*)"/', $linkHeader, $matches, PREG_UNMATCHED_AS_NULL); return [ @@ -320,8 +319,8 @@ private function extractLinkData(string $linkHeader) ]; } - /** @test */ - public function testCalculateProgrammeVoucherAmounts() + + public function testCalculateProgrammeVoucherAmounts(): void { $standardSponsor = factory(Sponsor::class)->create([ 'programme' => 0 @@ -337,14 +336,14 @@ public function testCalculateProgrammeVoucherAmounts() ]); $vouchers = $standardVouchers->concat($spVouchers); $programme_amounts = TraderController::calculateProgrammeVoucherAmounts($vouchers); - $this->assertEquals($programme_amounts, array ( + $this->assertEquals(array( 'standard' => 12, 'social_prescription' => 18, - )); + ), $programme_amounts); } - /** @test */ - public function testCalculateProgrammeVoucherAreaAmounts() + + public function testCalculateProgrammeVoucherAreaAmounts(): void { $standardSponsors = factory(Sponsor::class, 3)->create([ 'programme' => 0 diff --git a/tests/Unit/Controllers/Service/Admin/CentreControllerTest.php b/tests/Unit/Controllers/Service/Admin/CentreControllerTest.php index d1d0b699c..ffddbe268 100644 --- a/tests/Unit/Controllers/Service/Admin/CentreControllerTest.php +++ b/tests/Unit/Controllers/Service/Admin/CentreControllerTest.php @@ -40,8 +40,8 @@ public function setUp(): void ]; } - /** @test */ - public function testItCanStoreACentre() + + public function testItCanStoreACentre(): void { $this->actingAs($this->adminUser, 'admin') ->post( @@ -61,44 +61,43 @@ public function testItCanStoreACentre() $this->assertNotNull($c); } - /** @test */ - public function testICanSeeAnEditButtonOnTheListOfCentres() + + public function testICanSeeAnEditButtonOnTheListOfCentres(): void { - $centre = factory(Centre::class)->create([]); - $this->actingAs($this->adminUser, 'admin') - ->get(route('admin.centres.index')) - ->assertResponseOk() - ->seeInElement('h1', 'Children\'s Centres') - ->seeInElement('td', $centre->name) - ->seeInElement('a', 'Edit') - ; + $centre = factory(Centre::class)->create([]); + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centres.index')) + ->assertResponseOk() + ->seeInElement('h1', 'Children\'s Centres') + ->seeInElement('td', $centre->name) + ->seeInElement('a', 'Edit') + ; } - /** @test */ - public function testICanUpdateACentreName() + + public function testICanUpdateACentreName(): void { - $centre = factory(Centre::class)->create([]); - $data = [ - 'id' => $centre->id, - 'name' => 'New Centre Name' - ]; - $this->seeInDatabase('centres', [ - 'id' => $centre->id, - 'name' => $centre->name - ]); - $this->actingAs($this->adminUser, 'admin') - ->put( - route('admin.centres.update', ['id' => $centre->id]), - $data - ); - $this->seeInDatabase('centres', [ + $centre = factory(Centre::class)->create([]); + $data = [ 'id' => $centre->id, 'name' => 'New Centre Name' - ]); - $this->dontSeeInDatabase('centres', [ - 'id' => $centre->id, - 'name' => $centre->name - ]); - + ]; + $this->seeInDatabase('centres', [ + 'id' => $centre->id, + 'name' => $centre->name + ]); + $this->actingAs($this->adminUser, 'admin') + ->put( + route('admin.centres.update', ['id' => $centre->id]), + $data + ); + $this->seeInDatabase('centres', [ + 'id' => $centre->id, + 'name' => 'New Centre Name' + ]); + $this->dontSeeInDatabase('centres', [ + 'id' => $centre->id, + 'name' => $centre->name + ]); } } diff --git a/tests/Unit/Controllers/Service/Admin/DeliveriesControllerTest.php b/tests/Unit/Controllers/Service/Admin/DeliveriesControllerTest.php index 515f69d81..9e1b35641 100644 --- a/tests/Unit/Controllers/Service/Admin/DeliveriesControllerTest.php +++ b/tests/Unit/Controllers/Service/Admin/DeliveriesControllerTest.php @@ -30,11 +30,10 @@ public function setUp(): void } /** - * @test * * @return void */ - public function testStoreWithoutStartEndDateErrors() + public function testStoreWithoutStartEndDateErrors(): void { $this->actingAs($this->adminUser, 'admin') ->post($this->vouchersDeliveryroute, [ @@ -53,11 +52,10 @@ public function testStoreWithoutStartEndDateErrors() } /** - * @test * * @return void */ - public function testStoreStartEndSwapped() + public function testStoreStartEndSwapped(): void { $this->actingAs($this->adminUser, 'admin') ->post($this->vouchersDeliveryroute, [ @@ -74,11 +72,10 @@ public function testStoreStartEndSwapped() } /** - * @test * * @return void */ - public function testStoreCentreIsNotNumberErrors() + public function testStoreCentreIsNotNumberErrors(): void { $this->actingAs($this->adminUser, 'admin') ->post($this->vouchersDeliveryroute, [ @@ -93,11 +90,10 @@ public function testStoreCentreIsNotNumberErrors() } /** - * @test * * @return void */ - public function testStoreStartIsNotTheSameSponsorAsEndErrors() + public function testStoreStartIsNotTheSameSponsorAsEndErrors(): void { $this->actingAs($this->adminUser, 'admin') ->post($this->vouchersDeliveryroute, [ diff --git a/tests/Unit/Controllers/Service/Admin/PaymentControllerTest.php b/tests/Unit/Controllers/Service/Admin/PaymentControllerTest.php index 37f83717c..1b8059f65 100644 --- a/tests/Unit/Controllers/Service/Admin/PaymentControllerTest.php +++ b/tests/Unit/Controllers/Service/Admin/PaymentControllerTest.php @@ -17,7 +17,6 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\StoreTestCase; - class PaymentControllerTest extends StoreTestCase { use RefreshDatabase; @@ -35,12 +34,10 @@ public function setUp(): void // Create a Trader $this->trader = factory(Trader::class)->create(); - } - /** @test */ - public function testItReturnsASpecificPaymentRequest() + public function testItReturnsASpecificPaymentRequest(): void { //Create a token to pass to the route //Create some vouchers, give them a sponsor (as otherwise it might error) @@ -68,20 +65,19 @@ public function testItReturnsASpecificPaymentRequest() $data = $token->uuid; //pass the UUID to the route - $route = route('admin.payment-request.show',['paymentUuid'=>$data]); + $route = route('admin.payment-request.show', ['paymentUuid' => $data]); $this->actingAs($this->admin_user, 'admin') ->get($route) ->assertResponseStatus(200); - foreach($this->vouchers as $voucher){ + foreach ($this->vouchers as $voucher) { $this->see($voucher->code); } //TODO also test that I cannot see a different UUID in here } - /** @test */ - public function testItUpdatesASpecificPaymentRequest() + public function testItUpdatesASpecificPaymentRequest(): void { //Create a token to pass to the route //Create some vouchers, give them a sponsor (as otherwise it might error) @@ -120,6 +116,5 @@ public function testItUpdatesASpecificPaymentRequest() ->assertResponseStatus(200) ->seePageIs(route('admin.payments.index')) ->see('Vouchers Paid!'); - } } diff --git a/tests/Unit/Controllers/Service/Admin/SponsorControllerTest.php b/tests/Unit/Controllers/Service/Admin/SponsorControllerTest.php index 0c6466af8..4440eb835 100644 --- a/tests/Unit/Controllers/Service/Admin/SponsorControllerTest.php +++ b/tests/Unit/Controllers/Service/Admin/SponsorControllerTest.php @@ -9,6 +9,7 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Validation\Validator; use Tests\StoreTestCase; +use PHPUnit\Framework\Attributes\DataProvider; class SponsorControllerTest extends StoreTestCase { @@ -42,7 +43,7 @@ public function setUp(): void * @param $rules * @return mixed */ - protected function validate($mockedRequestData, $rules) + protected function validate($mockedRequestData, $rules): mixed { return $this->validator ->make($mockedRequestData, $rules) @@ -50,12 +51,11 @@ protected function validate($mockedRequestData, $rules) } /** - * @test - * @dataProvider storeValidationProvider * @param bool $shouldPass * @param array $mockedRequestData */ - public function testICannotSubmitInvalidValues($shouldPass, $mockedRequestData) + #[DataProvider('storeValidationProvider')] + public function testICannotSubmitInvalidValues(bool $shouldPass, array $mockedRequestData): void { $rules = (new AdminNewSponsorRequest())->rules(); @@ -146,8 +146,8 @@ public static function storeValidationProvider(): array ]; } - /** @test */ - public function testICanStoreASponsor() + + public function testICanStoreASponsor(): void { $adminUser = factory(AdminUser::class)->create(); @@ -181,8 +181,8 @@ public function testICanStoreASponsor() ]); } - /** @test */ - public function testItRedirectsBackOnError() + + public function testItRedirectsBackOnError(): void { $adminUser = factory(AdminUser::class)->create(); @@ -207,8 +207,8 @@ public function testItRedirectsBackOnError() ]); } - /** @test */ - public function testICanSeeProgrammeTypeOnSponsorListPage() + + public function testICanSeeProgrammeTypeOnSponsorListPage(): void { $adminUser = factory(AdminUser::class)->create(); $this->actingAs($adminUser, 'admin') @@ -220,8 +220,8 @@ public function testICanSeeProgrammeTypeOnSponsorListPage() ; } - /** @test */ - public function testIEditRuleValuesForAnSPSponsor() + + public function testIEditRuleValuesForAnSPSponsor(): void { $socialPrescribingRules = SponsorsController::socialPrescribingOverrides(); $this->socialPrescribingSponsor->evaluations()->saveMany($socialPrescribingRules); @@ -270,8 +270,8 @@ public function testIEditRuleValuesForAnSPSponsor() ; } - /** @test */ - public function testIEvaluationsAreCreatedIfTheyDoNotExistWhenIUpdate() + + public function testIEvaluationsAreCreatedIfTheyDoNotExistWhenIUpdate(): void { $adminUser = factory(AdminUser::class)->create(); $sponsor = $this->socialPrescribingSponsor; diff --git a/tests/Unit/Controllers/Service/Admin/VoucherControllerMysqlTest.php b/tests/Unit/Controllers/Service/Admin/VoucherControllerMysqlTest.php index 8409b142d..025e6f1f3 100644 --- a/tests/Unit/Controllers/Service/Admin/VoucherControllerMysqlTest.php +++ b/tests/Unit/Controllers/Service/Admin/VoucherControllerMysqlTest.php @@ -60,8 +60,8 @@ protected function setUp(): void Auth::logout(); } - /** @test */ - public function testItCanVoidVoucherCodes() + + public function testItCanVoidVoucherCodes(): void { // The post data $data = [ @@ -106,8 +106,8 @@ public function testItCanVoidVoucherCodes() }); } - /** @test */ - public function testItCanVoidVoucherCodesInABatchWithNonVoidables() + + public function testItCanVoidVoucherCodesInABatchWithNonVoidables(): void { // The post data $data = [ @@ -159,8 +159,8 @@ public function testItCanVoidVoucherCodesInABatchWithNonVoidables() }); } - /** @test */ - public function testItCanExpireVoucherCodes() + + public function testItCanExpireVoucherCodes(): void { { // The post data @@ -207,8 +207,8 @@ public function testItCanExpireVoucherCodes() } } - /** @test */ - public function testItCanExpireVoucherCodesInABatchWithNonExpireables() + + public function testItCanExpireVoucherCodesInABatchWithNonExpireables(): void { { // The post data @@ -262,8 +262,8 @@ public function testItCanExpireVoucherCodesInABatchWithNonExpireables() } } - /** @test */ - public function testAdminCanVoidASingleVoucher() + + public function testAdminCanVoidASingleVoucher(): void { $this->adminUser = factory(AdminUser::class)->create(); $this->voucherToVoid = factory(Voucher::class)->state('dispatched')->create(); @@ -276,10 +276,10 @@ public function testAdminCanVoidASingleVoucher() ->click('edit') ->seeInElement('h1', "Voucher Code: " . $this->voucherToVoid->code) ->press('transition') - ; - $this->seeInDatabase('vouchers', [ - 'code' => $this->voucherToVoid->code, - 'currentstate' => 'retired' - ]); + ; + $this->seeInDatabase('vouchers', [ + 'code' => $this->voucherToVoid->code, + 'currentstate' => 'retired' + ]); } } diff --git a/tests/Unit/Controllers/Service/Admin/VoucherControllerTest.php b/tests/Unit/Controllers/Service/Admin/VoucherControllerTest.php index 8e3daf100..5cdbe58a3 100644 --- a/tests/Unit/Controllers/Service/Admin/VoucherControllerTest.php +++ b/tests/Unit/Controllers/Service/Admin/VoucherControllerTest.php @@ -23,7 +23,7 @@ protected function setUp(): void $this->market = factory(Market::class)->create(); } - public function testStoreBatchWithoutStartEndSponsor() + public function testStoreBatchWithoutStartEndSponsor(): void { $this->actingAs($this->admin_user, 'admin') ->post(route('admin.vouchers.storebatch'), [ @@ -40,7 +40,7 @@ public function testStoreBatchWithoutStartEndSponsor() ]); } - public function testStoreBatchStartEndSwapped() + public function testStoreBatchStartEndSwapped(): void { $this->actingAs($this->admin_user, 'admin') ->post(route('admin.vouchers.storebatch'), [ @@ -55,7 +55,7 @@ public function testStoreBatchStartEndSwapped() ]); } - public function testStoreBatchInvalidSponsor() + public function testStoreBatchInvalidSponsor(): void { $this->actingAs($this->admin_user, 'admin') ->post(route('admin.vouchers.storebatch'), [ @@ -68,7 +68,7 @@ public function testStoreBatchInvalidSponsor() ]); } - public function testStoreBatchSuccessMsg() + public function testStoreBatchSuccessMsg(): void { $shortcode = $this->market->sponsor_shortcode; $start = '1'; @@ -88,7 +88,7 @@ public function testStoreBatchSuccessMsg() ->assertSessionHas('notification', $notification_msg); } - public function testStoreBatch() + public function testStoreBatch(): void { $shortcode = $this->market->sponsor_shortcode; $this->actingAs($this->admin_user, 'admin') @@ -114,7 +114,7 @@ public function testStoreBatch() } } - public function testItCanStoreZeroPaddedVouchersCorrectly() + public function testItCanStoreZeroPaddedVouchersCorrectly(): void { $shortcode = $this->market->sponsor_shortcode; $this->actingAs($this->admin_user, 'admin') diff --git a/tests/Unit/Controllers/Store/BundleControllerTest.php b/tests/Unit/Controllers/Store/BundleControllerTest.php index 9bc225bf3..c411cd87d 100644 --- a/tests/Unit/Controllers/Store/BundleControllerTest.php +++ b/tests/Unit/Controllers/Store/BundleControllerTest.php @@ -22,8 +22,7 @@ class BundleControllerTest extends StoreTestCase protected $centre; protected $centreUser; protected $testCodes; - /** @var Registration $registration */ - protected $registration; + protected Registration $registration; protected $bundle; protected function setUp(): void @@ -65,8 +64,8 @@ protected function setUp(): void Auth::logout(); } - /** @test */ - public function testICannotSubmitInvalidValuesToAppendVouchers() + + public function testICannotSubmitInvalidValuesToAppendVouchers(): void { $dataSets = [ // no data @@ -136,8 +135,8 @@ public function testICannotSubmitInvalidValuesToAppendVouchers() } } - /** @test */ - public function testIMustDisburseWithAllRelevantFields() + + public function testIMustDisburseWithAllRelevantFields(): void { $dataSets = [ [ @@ -207,8 +206,8 @@ public function testIMustDisburseWithAllRelevantFields() } } - /** @test */ - public function testICanAddManyVouchers() + + public function testICanAddManyVouchers(): void { $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); @@ -219,7 +218,7 @@ public function testICanAddManyVouchers() $post_route, [ 'start' => $this->testCodes[0], - 'end' => $this->testCodes[count($this->testCodes)-1] + 'end' => $this->testCodes[count($this->testCodes) - 1] ] ); @@ -235,20 +234,20 @@ public function testICanAddManyVouchers() $this->assertEquals(count($this->testCodes), $currentBundle->vouchers()->count()); } - /** @test */ - public function testICannotAddTooManyVouchersToABundle() + + public function testICannotAddTooManyVouchersToABundle(): void { $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); // Get the maxAdd value currently 101; - $overMaxAdd = config('arc.bundle_max_voucher_append')+1; + $overMaxAdd = config('arc.bundle_max_voucher_append') + 1; // Make the range 1-101, $startCode = "BIG00001"; $endCode = "BIG" . str_pad($overMaxAdd, 5, "0", STR_PAD_LEFT); $bigRange = Voucher::generateCodeRange($startCode, $endCode); - $this->assertEquals($overMaxAdd, count($bigRange)); + $this->assertCount($overMaxAdd, $bigRange); // Create the vouchers for the range; Auth::login($this->centreUser); @@ -292,8 +291,8 @@ public function testICannotAddTooManyVouchersToABundle() $this->assertEquals(0, $currentBundle->vouchers()->count()); } - /** @test */ - public function testICanAddSingleVouchers() + + public function testICanAddSingleVouchers(): void { $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); @@ -316,8 +315,8 @@ public function testICanAddSingleVouchers() } - /** @test */ - public function testICanDeleteTheCurrentBundle() + + public function testICanDeleteTheCurrentBundle(): void { /** @var Bundle $currentBundle */ $currentBundle = $this->registration->currentBundle(); @@ -368,8 +367,8 @@ public function testICanDeleteTheCurrentBundle() } } - /** @test */ - public function testICanDeleteANamedVoucher() + + public function testICanDeleteANamedVoucher(): void { /** @var Bundle $currentBundle */ $currentBundle = $this->registration->currentBundle(); @@ -411,7 +410,7 @@ public function testICanDeleteANamedVoucher() // refresh bundle $currentBundle->refresh(); // See less vouchers - $this->assertEquals(count($testCodes) -1, $currentBundle->vouchers()->count()); + $this->assertEquals(count($testCodes) - 1, $currentBundle->vouchers()->count()); // Refresh the detached voucher $voucher->refresh(); @@ -422,8 +421,8 @@ public function testICanDeleteANamedVoucher() $this->assertEquals('dispatched', $voucher->currentstate); } - /** @test */ - public function testICanSyncAnArrayOfVouchers() + + public function testICanSyncAnArrayOfVouchers(): void { $put_route = route('store.registration.vouchers.put', ['registration' => $this->registration->id]); @@ -458,8 +457,8 @@ public function testICanSyncAnArrayOfVouchers() $this->assertEquals(0, $currentBundle->vouchers()->count()); } - /** @test */ - public function testICannotDisburseAnEmptyBundle() + + public function testICannotDisburseAnEmptyBundle(): void { // Setup bundle $currentBundle = $this->registration->currentBundle(); @@ -509,8 +508,8 @@ public function testICannotDisburseAnEmptyBundle() ->assertResponseStatus(200); } - /** @test */ - public function testICannotAddAVoucherAllocatedInACentreIHaveAccessTo() + + public function testICannotAddAVoucherAllocatedInACentreIHaveAccessTo(): void { $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); @@ -553,15 +552,15 @@ public function testICannotAddAVoucherAllocatedInACentreIHaveAccessTo() $this->assertTrue($this->hasMatchingErrorMessage( Session::get('error_messages'), '~These vouchers are currently allocated to a different ' . $entity . '. Click on the voucher number to view the other ' . $entity . '\'s record: ' . $this->testCodes[0] . '~' - )); + )); // Check the expected error message is in the view $this->followRedirects() ->seeInElement('div[class="alert-message error"]', 'Click on the voucher number to view the other ' . $entity . '\'s record: ' . $this->testCodes[0] . ''); } - /** @test */ - public function testICannotAddAVoucherAllocatedInACentreIDoNotHaveAccessTo() + + public function testICannotAddAVoucherAllocatedInACentreIDoNotHaveAccessTo(): void { $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); @@ -626,8 +625,8 @@ public function testICannotAddAVoucherAllocatedInACentreIDoNotHaveAccessTo() ->seeInElement('div[class="alert-message error"]', 'These vouchers are allocated to a different ' . $entity . ' in a centre you can\'t access: ' . $this->testCodes[1]); } - /** @test */ - public function itCanAcceptAndCleanVouchersWithSpacesIn() + + public function testItCanAcceptAndCleanVouchersWithSpacesIn(): void { $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); @@ -654,8 +653,8 @@ public function itCanAcceptAndCleanVouchersWithSpacesIn() $this->assertEquals(1, $currentBundle->vouchers()->count()); } - /** @test */ - public function itHasSparseFormDataCleanedBeforeProcessing() + + public function testItHasSparseFormDataCleanedBeforeProcessing(): void { $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); @@ -708,7 +707,8 @@ public function itHasSparseFormDataCleanedBeforeProcessing() * @param string $regex * @return bool whether a matching message was found or not */ - private function hasMatchingErrorMessage($errorMessages, $regex) { + private function hasMatchingErrorMessage(array $errorMessages, string $regex): bool + { foreach ($errorMessages as $error) { // If the error message is an array describing some HTML, extract the text, otherwise use as a string directly. $string = is_array($error) && array_key_exists('html', $error) ? $error['html'] : $error; diff --git a/tests/Unit/Controllers/Store/StoreVoucherControllerTest.php b/tests/Unit/Controllers/Store/StoreVoucherControllerTest.php index 650613f1d..cba9264cf 100644 --- a/tests/Unit/Controllers/Store/StoreVoucherControllerTest.php +++ b/tests/Unit/Controllers/Store/StoreVoucherControllerTest.php @@ -23,7 +23,6 @@ class StoreVoucherControllerTest extends StoreTestCase * TODO : Test that decryption failing mid-stream results in a response that is well-marked as failed * ...although problems here would soon be spotted by the single person who uses this feature directly. */ - use RefreshDatabase; /** @var Centre $centre */ @@ -54,7 +53,7 @@ public function setUp(): void // Remove any file before we start if ($this->disk->exists($this->archiveName)) { - $this->disk->delete($this>$this->archiveName); + $this->disk->delete($this > $this->archiveName); } // Set up a Centre diff --git a/tests/Unit/Exceptions/ExceptionHandlerTest.php b/tests/Unit/Exceptions/ExceptionHandlerTest.php index 0e890e5bd..d4306842a 100644 --- a/tests/Unit/Exceptions/ExceptionHandlerTest.php +++ b/tests/Unit/Exceptions/ExceptionHandlerTest.php @@ -12,7 +12,6 @@ class ExceptionHandlerTest extends StoreTestCase { - private $exceptions = []; protected function setUp(): void @@ -24,8 +23,8 @@ protected function setUp(): void ]; } - /** @test */ - public function itShouldNotStackTraceInProduction() + + public function testItShouldNotStackTraceInProduction(): void { $handler = Container::getInstance()->make(Handler::class); try { @@ -40,8 +39,8 @@ public function itShouldNotStackTraceInProduction() } } - /** @test */ - public function itShouldStackTraceNotInProduction() + + public function testItShouldStackTraceNotInProduction(): void { $handler = Container::getInstance()->make(Handler::class); try { @@ -63,10 +62,10 @@ public function itShouldStackTraceNotInProduction() * @param object &$object Instantiated object that we will run method on. * @param string $methodName Method name to call * @param array $parameters Array of parameters to pass into method. - * @throws ReflectionException * @return mixed Method return. + * @throws ReflectionException */ - public function invokeMethod(&$object, $methodName, array $parameters = array()) + public function invokeMethod(object &$object, string $methodName, array $parameters = array()): mixed { $reflection = new ReflectionClass(get_class($object)); $method = $reflection->getMethod($methodName); diff --git a/tests/Unit/FormRequests/AdminNewCentreRequestTest.php b/tests/Unit/FormRequests/AdminNewCentreRequestTest.php index b37d8bcf4..b262f8c32 100644 --- a/tests/Unit/FormRequests/AdminNewCentreRequestTest.php +++ b/tests/Unit/FormRequests/AdminNewCentreRequestTest.php @@ -9,6 +9,7 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Validator; use Tests\StoreTestCase; +use PHPUnit\Framework\Attributes\DataProvider; class AdminNewCentreRequestTest extends StoreTestCase { @@ -30,9 +31,7 @@ private function validate(array $mockedRequestData): bool return Validator::make($mockedRequestData, $this->rules)->passes(); } - /** - * @dataProvider validationCases - */ + #[DataProvider('validationCases')] public function testItValidatesCentreRequests(bool $shouldPass, array $mockedRequestData): void { $this->assertEquals($shouldPass, $this->validate($mockedRequestData)); diff --git a/tests/Unit/FormRequests/AdminNewCentreUserRequestTest.php b/tests/Unit/FormRequests/AdminNewCentreUserRequestTest.php index da81f97fe..710bed323 100644 --- a/tests/Unit/FormRequests/AdminNewCentreUserRequestTest.php +++ b/tests/Unit/FormRequests/AdminNewCentreUserRequestTest.php @@ -6,6 +6,7 @@ use App\Http\Requests\AdminNewCentreUserRequest; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Validator; +use PHPUnit\Framework\Attributes\DataProvider; use Tests\StoreTestCase; class AdminNewCentreUserRequestTest extends StoreTestCase @@ -30,10 +31,10 @@ protected function validate($mockedRequestData, $rules): bool } /** - * @dataProvider storeValidationProvider * @param bool $shouldPass * @param array $mockedRequestData */ + #[DataProvider('storeValidationProvider')] public function testICannotSubmitInvalidValues(bool $shouldPass, array $mockedRequestData): void { $alternatives = $mockedRequestData['alternative_centres'] ?? null; diff --git a/tests/Unit/FormRequests/AdminNewUpdateMarketRequestTest.php b/tests/Unit/FormRequests/AdminNewUpdateMarketRequestTest.php index 5cac7bd02..030383d69 100644 --- a/tests/Unit/FormRequests/AdminNewUpdateMarketRequestTest.php +++ b/tests/Unit/FormRequests/AdminNewUpdateMarketRequestTest.php @@ -7,6 +7,7 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Validator; use Tests\StoreTestCase; +use PHPUnit\Framework\Attributes\DataProvider; class AdminNewUpdateMarketRequestTest extends StoreTestCase { @@ -27,9 +28,7 @@ private function validate(array $data): bool return Validator::make($data, $this->rules)->passes(); } - /** - * @dataProvider validationCases - */ + #[DataProvider('validationCases')] public function testItValidatesMarketRequests(bool $expected, array $data): void { $this->assertEquals($expected, $this->validate($data)); diff --git a/tests/Unit/FormRequests/AdminNewUpdateTraderRequestTest.php b/tests/Unit/FormRequests/AdminNewUpdateTraderRequestTest.php index fc7b8585a..582aef8af 100644 --- a/tests/Unit/FormRequests/AdminNewUpdateTraderRequestTest.php +++ b/tests/Unit/FormRequests/AdminNewUpdateTraderRequestTest.php @@ -9,6 +9,7 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Validator; use Tests\StoreTestCase; +use PHPUnit\Framework\Attributes\DataProvider; class AdminNewUpdateTraderRequestTest extends StoreTestCase { @@ -32,9 +33,7 @@ private function validate(array $data): bool return Validator::make($data, $this->rules)->passes(); } - /** - * @dataProvider validationCases - */ + #[DataProvider('validationCases')] public function testItValidatesTraderRequests(bool $expected, array $data): void { $this->assertEquals($expected, $this->validate($data)); diff --git a/tests/Unit/FormRequests/AdminUpdateVoucherRequestTest.php b/tests/Unit/FormRequests/AdminUpdateVoucherRequestTest.php index 9eb9ffd77..f05b034bb 100644 --- a/tests/Unit/FormRequests/AdminUpdateVoucherRequestTest.php +++ b/tests/Unit/FormRequests/AdminUpdateVoucherRequestTest.php @@ -9,6 +9,7 @@ use Auth; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Validator; +use PHPUnit\Framework\Attributes\DataProvider; use Tests\StoreTestCase; class AdminUpdateVoucherRequestTest extends StoreTestCase @@ -50,9 +51,7 @@ private function validate(array $data): bool return Validator::make($data, $this->rules)->passes(); } - /** - * @dataProvider validationCases - */ + #[DataProvider('validationCases')] public function testItValidatesVoucherUpdateRequests(bool $expected, array $data): void { $this->assertEquals($expected, $this->validate($data)); diff --git a/tests/Unit/FormRequests/ApiTransitionVoucherRequestTest.php b/tests/Unit/FormRequests/ApiTransitionVoucherRequestTest.php index 5596f0eb4..1ad8b7a3a 100644 --- a/tests/Unit/FormRequests/ApiTransitionVoucherRequestTest.php +++ b/tests/Unit/FormRequests/ApiTransitionVoucherRequestTest.php @@ -1,12 +1,12 @@ validator->make($mockedRequestData, $rules)->passes(); } /** - * @test - * @dataProvider storeValidationProvider * @param bool $shouldPass * @param array $mockedRequestData * @return void */ + #[DataProvider('storeValidationProvider')] public function testICannotSubmitInvalidValues(bool $shouldPass, array $mockedRequestData): void { // Copy the rules out of the FormRequest. diff --git a/tests/Unit/FormRequests/StoreNewRegistrationRequestTest.php b/tests/Unit/FormRequests/StoreNewRegistrationRequestTest.php index 8051d4bc7..2d1b64a73 100644 --- a/tests/Unit/FormRequests/StoreNewRegistrationRequestTest.php +++ b/tests/Unit/FormRequests/StoreNewRegistrationRequestTest.php @@ -5,6 +5,7 @@ use App\Http\Requests\StoreNewRegistrationRequest; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Validation\Validator; +use PHPUnit\Framework\Attributes\DataProvider; use Tests\StoreTestCase; class StoreNewRegistrationRequestTest extends StoreTestCase @@ -26,7 +27,7 @@ public function setUp(): void * @param $rules * @return mixed */ - protected function validate($mockedRequestData, $rules) + protected function validate($mockedRequestData, $rules): mixed { return $this->validator ->make($mockedRequestData, $rules) @@ -34,12 +35,11 @@ protected function validate($mockedRequestData, $rules) } /** - * @test - * @dataProvider storeValidationProvider * @param bool $shouldPass * @param array $mockedRequestData */ - public function testICannotSubmitInvalidValues($shouldPass, $mockedRequestData) + #[DataProvider('storeValidationProvider')] + public function testICannotSubmitInvalidValues(bool $shouldPass, array $mockedRequestData): void { // Copy the rules out of the FormRequest. $rules = (new StoreNewRegistrationRequest())->rules(); diff --git a/tests/Unit/FormRequests/StoreUpdateRegistrationRequestTest.php b/tests/Unit/FormRequests/StoreUpdateRegistrationRequestTest.php index 751967902..4fe687919 100644 --- a/tests/Unit/FormRequests/StoreUpdateRegistrationRequestTest.php +++ b/tests/Unit/FormRequests/StoreUpdateRegistrationRequestTest.php @@ -5,6 +5,7 @@ use App\Http\Requests\StoreUpdateRegistrationRequest; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Validation\Validator; +use PHPUnit\Framework\Attributes\DataProvider; use Tests\StoreTestCase; class StoreUpdateRegistrationRequestTest extends StoreTestCase @@ -26,7 +27,7 @@ public function setUp(): void * @param $rules * @return mixed */ - protected function validate($mockedRequestData, $rules) + protected function validate($mockedRequestData, $rules): mixed { return $this->validator ->make($mockedRequestData, $rules) @@ -34,12 +35,11 @@ protected function validate($mockedRequestData, $rules) } /** - * @test - * @dataProvider storeValidationProvider * @param bool $shouldPass * @param array $mockedRequestData */ - public function testICannotSubmitInvalidValues($shouldPass, $mockedRequestData) + #[DataProvider('storeValidationProvider')] + public function testICannotSubmitInvalidValues(bool $shouldPass, array $mockedRequestData): void { // Copy the rules out of the FormRequest. $rules = (new StoreUpdateRegistrationRequest())->rules(); diff --git a/tests/Unit/Listeners/CentreUserAuthenticatedTest.php b/tests/Unit/Listeners/CentreUserAuthenticatedTest.php index 83c8a47a8..1c3d5866d 100644 --- a/tests/Unit/Listeners/CentreUserAuthenticatedTest.php +++ b/tests/Unit/Listeners/CentreUserAuthenticatedTest.php @@ -35,18 +35,18 @@ protected function setUp(): void $this->sponsor = factory(Sponsor::class)->create(); $this->centre = factory(Centre::class)->create(['sponsor_id' => $this->sponsor->id]); - $this->centreUser = factory(CentreUser::Class)->create(); + $this->centreUser = factory(CentreUser::class)->create(); $this->centreUser->centres()->attach($this->centre->id, ['homeCentre' => true]); } - /** @test */ - public function it_does_nothing_for_non_centre_users(): void + + public function testItDoesNothingForNonCentreUsers(): void { Config::set('arc.default_to_home_centre', true); $listener = new CentreUserAuthenticated(); - $nonCentreUser = new class { + $nonCentreUser = new class () { public $homeCentre = null; }; @@ -55,8 +55,8 @@ public function it_does_nothing_for_non_centre_users(): void $this->assertTrue(Session::missing(self::KEY)); } - /** @test */ - public function it_sets_session_to_all_when_config_is_false_and_key_is_missing(): void + + public function testItSetsSessionToAllWhenConfigIsFalseAndKeyIsMissing(): void { Config::set('arc.default_to_home_centre', false); @@ -69,8 +69,8 @@ public function it_sets_session_to_all_when_config_is_false_and_key_is_missing() $this->assertSame('all', Session::get(self::KEY)); } - /** @test */ - public function it_sets_session_to_home_centre_id_when_config_is_true_and_key_is_missing(): void + + public function testItSetsSessionToHomeCentreIdWhenConfigIsTrueAndKeyIsMissing(): void { Config::set('arc.default_to_home_centre', true); @@ -83,8 +83,8 @@ public function it_sets_session_to_home_centre_id_when_config_is_true_and_key_is $this->assertSame($this->centre->id, Session::get(self::KEY)); } - /** @test */ - public function it_sets_session_to_null_when_config_is_true_and_home_centre_is_null(): void + + public function testItSetsSessionToNullWhenConfigIsTrueAndHomeCentreIsNull(): void { Config::set('arc.default_to_home_centre', true); @@ -99,8 +99,8 @@ public function it_sets_session_to_null_when_config_is_true_and_home_centre_is_n $this->assertNull(Session::get(self::KEY)); } - /** @test */ - public function it_does_not_override_existing_session_value(): void + + public function testItDoesNotOverrideExistingSessionValue(): void { Config::set('arc.default_to_home_centre', true); diff --git a/tests/Unit/Middleware/Empty304Test.php b/tests/Unit/Middleware/Empty304Test.php index 30e7c743d..cf7c11986 100644 --- a/tests/Unit/Middleware/Empty304Test.php +++ b/tests/Unit/Middleware/Empty304Test.php @@ -9,7 +9,6 @@ class Empty304Test extends TestCase { - /** @test */ public function testContentIsCleared(): void { $request = new Request(); @@ -24,4 +23,4 @@ public function testContentIsCleared(): void $this->assertEmpty($response->getContent()); } -} \ No newline at end of file +} diff --git a/tests/Unit/Models/BundleModelTest.php b/tests/Unit/Models/BundleModelTest.php index 48d284f84..35f27fe15 100644 --- a/tests/Unit/Models/BundleModelTest.php +++ b/tests/Unit/Models/BundleModelTest.php @@ -17,11 +17,9 @@ class BundleModelTest extends TestCase { - use RefreshDatabase; - /** @var Bundle bundle */ - protected $bundle; + protected Bundle $bundle; protected function setUp(): void { parent::setUp(); @@ -29,7 +27,7 @@ protected function setUp(): void $this->bundle = factory(Bundle::class)->create(); } - public function testBundleIsCreatedWithExpectedAttributes() + public function testBundleIsCreatedWithExpectedAttributes(): void { $b = $this->bundle; $this->assertInstanceOf(Bundle::class, $b); @@ -44,16 +42,16 @@ public function testBundleIsCreatedWithExpectedAttributes() $this->assertEmpty($b->vouchers); } - public function testPopulatedBundleIsCreatedWithExpectedAttributes() + public function testPopulatedBundleIsCreatedWithExpectedAttributes(): void { $centre = factory(Centre::class)->create(); - $user = factory(CentreUser::class)->create(['centre_id'=>$centre->id]); + $user = factory(CentreUser::class)->create(['centre_id' => $centre->id]); Auth::login($user); // family and carer needed to collect the bundle - $family = factory(Family::class)->create(['initial_centre_id'=>$user->centre_id]); - $carer = factory(Carer::class)->create(['name'=>'Bob','family_id'=>$family->id]); + $family = factory(Family::class)->create(['initial_centre_id' => $user->centre_id]); + $carer = factory(Carer::class)->create(['name' => 'Bob','family_id' => $family->id]); //create three vouchers and transition to collected. $vs = factory(Voucher::class, 3)->state('printed') @@ -81,7 +79,7 @@ public function testPopulatedBundleIsCreatedWithExpectedAttributes() $this->assertNotEmpty($disbursedBundle->vouchers); } - public function testBundleCanHaveManyVouchers() + public function testBundleCanHaveManyVouchers(): void { $user = factory(CentreUser::class)->create(); Auth::login($user); @@ -96,8 +94,8 @@ public function testBundleCanHaveManyVouchers() $this->assertEquals($vs->count(), $this->bundle->vouchers()->count()); } - /** @test */ - public function testItCanGetOnlyDisbursedBundles() + + public function testItCanGetOnlyDisbursedBundles(): void { // Make a registration $registration = factory(Registration::class)->create(); @@ -124,8 +122,8 @@ public function testItCanGetOnlyDisbursedBundles() $this->assertEquals($disbursedBundles->count(), $registration->bundles()->disbursed()->count()); } - /** @test */ - public function testItCannotAlterTheBundleToIncludeADisbursedVoucher() + + public function testItCannotAlterTheBundleToIncludeADisbursedVoucher(): void { $user = factory(CentreUser::class)->create(); Auth::login($user); diff --git a/tests/Unit/Models/CarerModelTest.php b/tests/Unit/Models/CarerModelTest.php index a443fdd4d..9adc62c6d 100644 --- a/tests/Unit/Models/CarerModelTest.php +++ b/tests/Unit/Models/CarerModelTest.php @@ -9,18 +9,17 @@ class CarerModelTest extends TestCase { - use RefreshDatabase; - /** @test */ - public function itHasExpectedAttributes() + + public function testItHasExpectedAttributes(): void { $carer = factory(Carer::class)->make(); $this->assertNotNull($carer->name); } - /** @test */ - public function itCanHaveAFamily() + + public function testItCanHaveAFamily(): void { // Make a Family $family = factory(Family::class)->create(); diff --git a/tests/Unit/Models/CentreModelTest.php b/tests/Unit/Models/CentreModelTest.php index 6574ff263..caa831a3f 100644 --- a/tests/Unit/Models/CentreModelTest.php +++ b/tests/Unit/Models/CentreModelTest.php @@ -14,8 +14,8 @@ class CentreModelTest extends TestCase { use RefreshDatabase; - /** @test */ - public function itHasExpectedAttributes() + + public function testItHasExpectedAttributes(): void { $centre = factory(Centre::class)->make(); $this->assertNotNull($centre->name); @@ -23,8 +23,8 @@ public function itHasExpectedAttributes() $this->assertContains($centre->print_pref, config('arc.print_preferences')); } - /** @test */ - public function itHasASponsor() + + public function testItHasASponsor(): void { $centre = factory(Centre::class)->create([ 'sponsor_id' => factory(Sponsor::class)->create()->id, @@ -32,8 +32,8 @@ public function itHasASponsor() $this->assertInstanceOf(Sponsor::class, $centre->sponsor); } - /** @test */ - public function itCanHaveRegistrations() + + public function testItCanHaveRegistrations(): void { $centre = factory(Centre::class)->create(); factory(Registration::class, 3)->create([ @@ -44,8 +44,8 @@ public function itCanHaveRegistrations() $this->assertInstanceOf(Registration::class, $registrations[0]); } - /** @test */ - public function itCanHaveNoRegistrations() + + public function testItCanHaveNoRegistrations(): void { $centre = factory(Centre::class)->create(); $registrations = $centre->registrations; @@ -53,8 +53,8 @@ public function itCanHaveNoRegistrations() $this->assertEquals(0, $registrations->count()); } - /** @test */ - public function itCanHaveUsers() + + public function testItCanHaveUsers(): void { $centre = factory(Centre::class)->create(); @@ -73,8 +73,8 @@ function (CentreUser $centreUser) use ($centre) { $this->assertInstanceOf(CentreUser::class, $centreUsers[0]); } - /** @test */ - public function itCanHaveNeighbours() + + public function testItCanHaveNeighbours(): void { $sponsor_a = factory(Sponsor::class)->create(); $sponsor_b = factory(Sponsor::class)->create(); diff --git a/tests/Unit/Models/CentreUserModelTest.php b/tests/Unit/Models/CentreUserModelTest.php index 1830c052d..8d316670a 100644 --- a/tests/Unit/Models/CentreUserModelTest.php +++ b/tests/Unit/Models/CentreUserModelTest.php @@ -21,8 +21,8 @@ protected function setUp(): void $this->notes = factory(Note::class, 2)->create(['user_id' => $this->centreUser->id]); } - /** @test */ - public function testCentreUserHasExpectedAttributes() + + public function testCentreUserHasExpectedAttributes(): void { $cu = $this->centreUser; $this->assertNotNull($cu->name); @@ -32,14 +32,14 @@ public function testCentreUserHasExpectedAttributes() $this->assertFalse($cu->downloader); } - /** @test */ - public function testCentreUserCanHaveNotes() + + public function testCentreUserCanHaveNotes(): void { $this->assertCount(2, $this->centreUser->notes); } - /**@test */ - public function testCentreUserCanHaveDownloadTrue() + /** */ + public function testCentreUserCanHaveDownloadTrue(): void { // Standard CU $cu = $this->centreUser; @@ -54,8 +54,8 @@ public function testCentreUserCanHaveDownloadTrue() $this->assertTrue($cu->downloader); } - /** @test */ - public function testCentreUserCanHaveAHomeCentre() + + public function testCentreUserCanHaveAHomeCentre(): void { $cu = $this->centreUser; // Has no centres; @@ -72,8 +72,8 @@ public function testCentreUserCanHaveAHomeCentre() $this->assertEquals($centre->id, $cu->homeCentre->id); } - /** @test */ - public function testCentreUserCanHaveAlternativeCentres() + + public function testCentreUserCanHaveAlternativeCentres(): void { $cu = $this->centreUser; // Has no centres; diff --git a/tests/Unit/Models/ChildModelTest.php b/tests/Unit/Models/ChildModelTest.php index 9040df03c..1a1856564 100644 --- a/tests/Unit/Models/ChildModelTest.php +++ b/tests/Unit/Models/ChildModelTest.php @@ -10,11 +10,10 @@ class ChildModelTest extends TestCase { - use RefreshDatabase; - /** @test */ - public function itHasExpectedAttributes() + + public function testItHasExpectedAttributes(): void { $child = factory(Child::class)->make(); $this->assertNotNull($child->dob); @@ -22,8 +21,8 @@ public function itHasExpectedAttributes() $this->assertNull($child->verfied); } - /** @test */ - public function itCanBeVerified() + + public function testItCanBeVerified(): void { $child1 = factory(Child::class)->states('verified')->make(); $this->assertNotNull($child1->verified); @@ -34,8 +33,8 @@ public function itCanBeVerified() $this->assertFalse($child2->verified); } - /** @test */ - public function itCanHaveAFamily() + + public function testItCanHaveAFamily(): void { // Make a Family with a Child. $family = factory(Family::class)->create(); @@ -47,8 +46,8 @@ public function itCanHaveAFamily() $this->assertEquals($family->id, $child->family->id); } - /** @test */ - public function itHasAMethodThatCalculatesSchoolAge() + + public function testItHasAMethodThatCalculatesSchoolAge(): void { // Use app.school_month to set the expected "start month". $school_month = config('arc.school_month'); @@ -56,7 +55,7 @@ public function itHasAMethodThatCalculatesSchoolAge() // Create a child born before 1st of app.school_month $child = new Child([ "born" => 'true', - "dob" => Carbon::createFromDate('2017', ($school_month -1), '1')->toDateTimeString(), + "dob" => Carbon::createFromDate('2017', ($school_month - 1), '1')->toDateTimeString(), ]); // Check his school month is app.school_month 2021 diff --git a/tests/Unit/Models/DeliveryModelTest.php b/tests/Unit/Models/DeliveryModelTest.php index 9f742d411..655fba529 100644 --- a/tests/Unit/Models/DeliveryModelTest.php +++ b/tests/Unit/Models/DeliveryModelTest.php @@ -22,8 +22,8 @@ protected function setUp(): void $this->delivery = factory(Delivery::class)->create(); } - /** @test */ - public function testDeliveryIsCreatedWithExpectedAttributes() + + public function testDeliveryIsCreatedWithExpectedAttributes(): void { $d = $this->delivery; $this->assertInstanceOf(Delivery::class, $d); @@ -35,8 +35,8 @@ public function testDeliveryIsCreatedWithExpectedAttributes() $this->assertEmpty($d->range); } - /** @test */ - public function testPopulatedDeliveryIsCreatedWithExpectedAttributes() + + public function testPopulatedDeliveryIsCreatedWithExpectedAttributes(): void { $centre = factory(Centre::class)->create(); @@ -62,8 +62,8 @@ public function testPopulatedDeliveryIsCreatedWithExpectedAttributes() $this->assertNotEmpty($dispatchedBundle->vouchers); } - /** @test */ - public function testDeliveryCanHaveManyVouchers() + + public function testDeliveryCanHaveManyVouchers(): void { // Create three vouchers and transition to dipatched. $vs = factory(Voucher::class, 3)->state('printed') @@ -76,14 +76,14 @@ public function testDeliveryCanHaveManyVouchers() $this->assertEquals($vs->count(), $this->delivery->vouchers()->count()); } - /** @test */ - public function testDeliveryBelongsToACentre() + + public function testDeliveryBelongsToACentre(): void { $this->assertInstanceOf('App\Centre', $this->delivery->centre); } - /** @test */ - public function testOrderDeliveriesByField() + + public function testOrderDeliveriesByField(): void { factory(Delivery::class)->create([ 'centre_id' => factory(Centre::class)->create(['name' => 'Adriatic Centre'])->id, diff --git a/tests/Unit/Models/FamilyModelTest.php b/tests/Unit/Models/FamilyModelTest.php index dbee8eea1..aa139a279 100644 --- a/tests/Unit/Models/FamilyModelTest.php +++ b/tests/Unit/Models/FamilyModelTest.php @@ -14,8 +14,8 @@ class FamilyModelTest extends TestCase { use RefreshDatabase; - /** @test */ - public function itCanHaveRegistrations() + + public function testItCanHaveRegistrations(): void { // Create Family $family = factory(Family::class)->create(); @@ -47,8 +47,8 @@ public function itCanHaveRegistrations() } } - /** @test */ - public function itCanHaveCarers() + + public function testItCanHaveCarers(): void { // Create Family $family = factory(Family::class)->create(); @@ -67,8 +67,8 @@ public function itCanHaveCarers() } } - /** @test */ - public function itCanHaveChildren() + + public function testItCanHaveChildren(): void { // Create Family $family = factory(Family::class)->create(); @@ -88,8 +88,8 @@ public function itCanHaveChildren() } } - /** @test */ - public function itCanAppendItsPrimaryCarerName() + + public function testItCanAppendItsPrimaryCarerName(): void { // Make a family with carers $family = factory(Family::class)->create(); @@ -117,8 +117,8 @@ public function itCanAppendItsPrimaryCarerName() $this->assertEquals($pri_carer->name, $pri_carer_family->pri_carer); } - /** @test */ - public function itHasAnAttributeThatReturnsNearestDueDateOrNull() + + public function testItHasAnAttributeThatReturnsNearestDueDateOrNull(): void { // Create Family $family = factory(Family::class)->create([]); @@ -130,7 +130,7 @@ public function itHasAnAttributeThatReturnsNearestDueDateOrNull() $family->children() ->saveMany( collect([ - factory(Child::class,2 )->state('underOne')->make(), + factory(Child::class, 2)->state('underOne')->make(), factory(Child::class)->state('betweenOneAndPrimarySchoolAge')->make(), factory(Child::class)->state('isSecondarySchoolAge')->make(), ])->flatten() @@ -154,8 +154,8 @@ public function itHasAnAttributeThatReturnsNearestDueDateOrNull() $this->assertEquals($pregnancy->dob, $pregnant_family->expecting); } - /** @test */ - public function itCanGenreateAndSetAnRvidCorrectly() + + public function testItCanGenreateAndSetAnRvidCorrectly(): void { // Set up some families and centres. $centre1 = factory(Centre::class)->create(); @@ -238,8 +238,8 @@ public function itCanGenreateAndSetAnRvidCorrectly() ]); } - /** @test */ - public function itCanGetsARvidCorrectlyForGivenCentre() + + public function testItCanGetsARvidCorrectlyForGivenCentre(): void { $centre = factory(Centre::class)->create(); $family = factory(Family::class)->create(); diff --git a/tests/Unit/Models/MarketModelTest.php b/tests/Unit/Models/MarketModelTest.php index 4c68435b2..0592db20a 100644 --- a/tests/Unit/Models/MarketModelTest.php +++ b/tests/Unit/Models/MarketModelTest.php @@ -22,7 +22,7 @@ protected function setUp(): void $this->sponsor = $this->market->sponsor; } - public function testMarketIsCreatedWithExpectedAttributes() + public function testMarketIsCreatedWithExpectedAttributes(): void { $m = $this->market; // Keeping it simple to make writing test suite less onerous. @@ -36,25 +36,25 @@ public function testMarketIsCreatedWithExpectedAttributes() $this->assertIsInt($m->sponsor_id); } - public function testMarketBelongsToSponsor() + public function testMarketBelongsToSponsor(): void { $this->assertInstanceOf(BelongsTo::class, $this->market->sponsor()); $this->assertInstanceOf(Sponsor::class, $this->market->sponsor); } - public function testMarketCanHaveManyTraders() + public function testMarketCanHaveManyTraders(): void { $this->assertInstanceOf(HasMany::class, $this->market->traders()); } - public function testGetSponsorShortcodeAttribute() + public function testGetSponsorShortcodeAttribute(): void { $shortcode_market = $this->market->sponsor_shortcode; $shortcode_sponsor = $this->sponsor->shortcode; $this->assertEquals($shortcode_sponsor, $shortcode_market); } - public function testSoftDeleteMarket() + public function testSoftDeleteMarket(): void { $this->market->delete(); $this->assertCount(1, Market::withTrashed()->get()); diff --git a/tests/Unit/Models/RegistrationModelTest.php b/tests/Unit/Models/RegistrationModelTest.php index 66a40f30e..5fd4e7981 100644 --- a/tests/Unit/Models/RegistrationModelTest.php +++ b/tests/Unit/Models/RegistrationModelTest.php @@ -13,13 +13,13 @@ class RegistrationModelTest extends TestCase { use RefreshDatabase; - /** @test */ - public function itCanBeCreated() + + public function testItCanBeCreated(): void { $family = factory(Family::class)->create(); $centre = factory(Centre::class)->create(); - $registration = new Registration; + $registration = new Registration(); $registration->centre_id = $centre->id; $registration->eligibility_hsbs = "healthy-start-applying"; $registration->eligibility_nrpf = "no"; @@ -28,8 +28,8 @@ public function itCanBeCreated() $this->assertTrue($registration->save()); } - /** @test */ - public function itCanReturnRegistrationsOnlyForActiveFamilies() + + public function testItCanReturnRegistrationsOnlyForActiveFamilies(): void { // Create a centre $centre = factory(Centre::class)->create(); @@ -40,7 +40,7 @@ public function itCanReturnRegistrationsOnlyForActiveFamilies() ]); // Check that we have 4. - $this->assertEquals(Registration::whereActiveFamily()->count(), 4); + $this->assertEquals(4, Registration::whereActiveFamily()->count()); // A family has left. $family = $registrations->first()->family; @@ -48,6 +48,6 @@ public function itCanReturnRegistrationsOnlyForActiveFamilies() $family->save(); // check there are only 3. - $this->assertEquals(Registration::whereActiveFamily()->count(), 3); + $this->assertEquals(3, Registration::whereActiveFamily()->count()); } } diff --git a/tests/Unit/Models/SponsorModelTest.php b/tests/Unit/Models/SponsorModelTest.php index c74a3286f..2717e4b52 100644 --- a/tests/Unit/Models/SponsorModelTest.php +++ b/tests/Unit/Models/SponsorModelTest.php @@ -20,7 +20,7 @@ protected function setUp(): void $this->sponsor = factory(Sponsor::class)->create()->fresh(); } - public function testSponsorIsCreatedWithExpectedAttributes() + public function testSponsorIsCreatedWithExpectedAttributes(): void { $s = $this->sponsor; // Keeping it simple to make writing test suite less onerous. @@ -32,33 +32,33 @@ public function testSponsorIsCreatedWithExpectedAttributes() $this->assertIsInt($s->programme); } - public function testItCanGetItsProgramName() + public function testItCanGetItsProgramName(): void { $s = $this->sponsor; $this->assertEquals($s->programme_name, config('arc.programmes')[$s->programme]); } - public function testSoftDeleteSponsor() + public function testSoftDeleteSponsor(): void { $this->sponsor->delete(); $this->assertCount(1, Sponsor::withTrashed()->get()); $this->assertCount(0, Sponsor::all()); } - public function testSponsorHasManyVouchers() + public function testSponsorHasManyVouchers(): void { factory(Voucher::class, 10)->create([ 'sponsor_id' => $this->sponsor->id, ]); factory(Voucher::class, 2)->create([ - 'sponsor_id' => $this->sponsor->id +1, + 'sponsor_id' => $this->sponsor->id + 1, ]); $this->assertCount(10, $this->sponsor->vouchers); $this->assertNotEquals($this->sponsor->vouchers, Voucher::all()); } - /** @test */ - public function itCanHaveCentres() + + public function testItCanHaveCentres(): void { // Make a sponsor $s = $this->sponsor; @@ -78,8 +78,8 @@ public function itCanHaveCentres() } } - /** @test */ - public function itCanHaveEvaluations() + + public function testItCanHaveEvaluations(): void { // Make a sponsor $s = $this->sponsor; diff --git a/tests/Unit/Models/StateTokenModelTest.php b/tests/Unit/Models/StateTokenModelTest.php index b5bee8663..f5b1f0d39 100644 --- a/tests/Unit/Models/StateTokenModelTest.php +++ b/tests/Unit/Models/StateTokenModelTest.php @@ -12,8 +12,8 @@ class StateTokenModelTest extends TestCase { use RefreshDatabase; - /** @test */ - public function testItCanGenerateAPaymentUUID() + + public function testItCanGenerateAPaymentUUID(): void { // Create a StateToken $token = factory(StateToken::class)->create(); @@ -31,8 +31,8 @@ public function testItCanGenerateAPaymentUUID() $this->assertEquals($uuid, $specifiedToken->uuid); } - /** @test */ - public function testItCannotSaveADuplicateUUID() + + public function testItCannotSaveADuplicateUUID(): void { $this->expectExceptionMessage("Integrity constraint violation: 19 UNIQUE constraint failed: state_tokens.uuid"); $this->expectException(QueryException::class); diff --git a/tests/Unit/Models/TraderModelTest.php b/tests/Unit/Models/TraderModelTest.php index e30fe9fd5..94636346e 100644 --- a/tests/Unit/Models/TraderModelTest.php +++ b/tests/Unit/Models/TraderModelTest.php @@ -23,7 +23,7 @@ protected function setUp(): void $this->trader = factory(Trader::class)->state('withnullable')->create(); } - public function testTraderIsCreatedWithExpectedAttributes() + public function testTraderIsCreatedWithExpectedAttributes(): void { $t = $this->trader; // Keeping it simple to make writing test suite less onerous. @@ -36,14 +36,14 @@ public function testTraderIsCreatedWithExpectedAttributes() $this->assertNull($t->disabled_at); } - public function testSoftDeleteTrader() + public function testSoftDeleteTrader(): void { $this->trader->delete(); $this->assertCount(1, Trader::withTrashed()->get()); $this->assertCount(0, Trader::all()); } - public function testItCanBeDisabledAndEnabled() + public function testItCanBeDisabledAndEnabled(): void { $this->trader->disable(); $this->trader->refresh(); @@ -54,12 +54,12 @@ public function testItCanBeDisabledAndEnabled() $this->assertNull($this->trader->disabled_at); } - public function testTraderBelongsToMarket() + public function testTraderBelongsToMarket(): void { $this->assertInstanceOf(Market::class, $this->trader->market); } - public function testTraderHasManyVouchers() + public function testTraderHasManyVouchers(): void { factory(Voucher::class, 10)->create([ 'trader_id' => $this->trader->id, @@ -71,7 +71,7 @@ public function testTraderHasManyVouchers() $this->assertNotEquals($this->trader->vouchers, Voucher::all()); } - public function testTraderHasConfirmedVouchers() + public function testTraderHasConfirmedVouchers(): void { $vouchers = factory(Voucher::class, 3)->state('printed')->create([ 'trader_id' => $this->trader->id, @@ -102,7 +102,7 @@ public function testTraderHasConfirmedVouchers() $this->assertEquals($confirmed_codes, $vc_code_states); } - public function testTraderHasVouchersWithStatus() + public function testTraderHasVouchersWithStatus(): void { $vouchers = factory(Voucher::class, 6)->state('printed')->create([ 'trader_id' => $this->trader->id, diff --git a/tests/Unit/Models/UserModelTest.php b/tests/Unit/Models/UserModelTest.php index 27f9669bb..0d346ca83 100644 --- a/tests/Unit/Models/UserModelTest.php +++ b/tests/Unit/Models/UserModelTest.php @@ -23,20 +23,20 @@ protected function setUp(): void $this->users[1]->traders()->sync([4,5]); } - public function testUserBelongsToManyTraders() + public function testUserBelongsToManyTraders(): void { $this->assertCount(3, $this->users[0]->traders); $this->assertCount(2, $this->users[1]->traders); } - public function testSoftDeleteUser() + public function testSoftDeleteUser(): void { $this->users[0]->delete(); $this->assertCount(2, User::withTrashed()->get()); $this->assertCount(1, User::all()); } - public function testCheckIfTraderBelongsToUser() + public function testCheckIfTraderBelongsToUser(): void { $trader = $this->traders[0]; $this->assertTrue($this->users[0]->hasEnabledTrader($trader)); diff --git a/tests/Unit/Models/VoucherModelTest.php b/tests/Unit/Models/VoucherModelTest.php index 3b9b4284b..c1daec02b 100644 --- a/tests/Unit/Models/VoucherModelTest.php +++ b/tests/Unit/Models/VoucherModelTest.php @@ -12,6 +12,8 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Testing\RefreshDatabase; +use ReflectionClass; +use ReflectionException; use SM\StateMachine\StateMachine; use Tests\TestCase; @@ -26,15 +28,15 @@ protected function setUp(): void $this->voucher = factory(Voucher::class)->state('dispatched')->create(); } - /** @test */ - public function testAVoucherIsCreatedWithExpectedAttributes() + + public function testAVoucherIsCreatedWithExpectedAttributes(): void { $v = $this->voucher; // Keeping it simple to make writing test suite less onerous. // The default error returned by asserts will be enough. $this->assertInstanceOf(Voucher::class, $v); $this->assertNotNull($v->code); - $this->assertTrue(in_array($v->currentstate, config('state-machine.Voucher.states'))); + $this->assertContains($v->currentstate, config('state-machine.Voucher.states')); $this->assertNotNull($v->sponsor_id); $this->assertIsInt($v->sponsor_id); @@ -44,29 +46,29 @@ public function testAVoucherIsCreatedWithExpectedAttributes() $this->assertIsInt($v->trader_id); } - /** @test */ - public function testCreateVoucherStateMachine() + + public function testCreateVoucherStateMachine(): void { // Check there's an FSM for the model $this->assertInstanceOf(StateMachine::class, $this->voucher->getStateMachine()); } - /** @test */ - public function testSoftDeleteVoucher() + + public function testSoftDeleteVoucher(): void { $this->voucher->delete(); $this->assertCount(1, Voucher::withTrashed()->get()); $this->assertCount(0, Voucher::all()); } - /** @test */ - public function testVoucherBelongsToSponsor() + + public function testVoucherBelongsToSponsor(): void { $this->assertInstanceOf(Sponsor::class, $this->voucher->sponsor); } - /** @test */ - public function testVoucherCanBelongToTrader() + + public function testVoucherCanBelongToTrader(): void { // The voucher factory creates a sponsor because it's required. // But not a Trader which is nullable. @@ -76,8 +78,8 @@ public function testVoucherCanBelongToTrader() $this->assertInstanceOf(Trader::class, $voucher->trader); } - /** @test */ - public function testVoucherCanBelongToDelivery() + + public function testVoucherCanBelongToDelivery(): void { $voucher = factory(Voucher::class)->create([ 'delivery_id' => factory(Delivery::class)->create()->id, @@ -85,8 +87,8 @@ public function testVoucherCanBelongToDelivery() $this->assertInstanceOf(Delivery::class, $voucher->delivery); } - /** @test */ - public function testFindVoucherByCode() + + public function testFindVoucherByCode(): void { $a = factory(Voucher::class)->create([ 'code' => 'aaaaa', @@ -102,8 +104,8 @@ public function testFindVoucherByCode() $this->assertNotEquals($b->fresh(), Voucher::findByCode('aaaaa')); } - /** @test */ - public function testGetVoucherPendedOnDay() + + public function testGetVoucherPendedOnDay(): void { $v = $this->voucher; $user = factory(User::class)->create(); @@ -117,8 +119,8 @@ public function testGetVoucherPendedOnDay() ); } - /** @test */ - public function testGetVoucherRecordedOnDay() + + public function testGetVoucherRecordedOnDay(): void { $v = $this->voucher; $user = factory(User::class)->create(); @@ -131,8 +133,8 @@ public function testGetVoucherRecordedOnDay() ); } - /** @test */ - public function testGetVoucherReimbursedOnDay() + + public function testGetVoucherReimbursedOnDay(): void { $v = $this->voucher; $user = factory(User::class)->create(); @@ -147,8 +149,8 @@ public function testGetVoucherReimbursedOnDay() ); } - /** @test */ - public function testCleanVouchers() + + public function testCleanVouchers(): void { $user = factory(User::class)->create(); Auth::login($user); @@ -180,8 +182,8 @@ public function testCleanVouchers() $this->assertEquals(count($badCodes), $vouchers->count()); } - /** @test */ - public function testScopeConfirmedVouchers() + + public function testScopeConfirmedVouchers(): void { $user = factory(User::class)->create(); Auth::login($user); @@ -207,9 +209,8 @@ public function testScopeConfirmedVouchers() /** * Here because I can't work out how to test the Stateable Trait well * - * @test */ - public function testItCanCreateAValidTransitionDefinition() + public function testItCanCreateAValidTransitionDefinition(): void { $validTransDef = Voucher::createTransitionDef("printed", "dispatch"); @@ -224,8 +225,8 @@ public function testItCanCreateAValidTransitionDefinition() $this->assertNull(Voucher::createTransitionDef("kensington", "dispatched")); } - /** @test */ - public function testItCanCreateARangeDef() + + public function testItCanCreateARangeDef(): void { // Make some vouchers $sponsor = factory(Sponsor::class)->create([ @@ -256,8 +257,8 @@ public function testItCanCreateARangeDef() Voucher::createRangeDefFromVoucherCodes('INV999998', 'INV999999'); } - /** @test */ - public function testItCanFindASupersetRangeFromARangeSet() + + public function testItCanFindASupersetRangeFromARangeSet(): void { $rangeCodes = [ 'TST0101', @@ -322,11 +323,11 @@ public function testItCanFindASupersetRangeFromARangeSet() public function invokeMethod(&$object, $methodName, array $parameters = array()) { try { - $reflection = new \ReflectionClass(get_class($object)); + $reflection = new ReflectionClass(get_class($object)); $method = $reflection->getMethod($methodName); $method->setAccessible(true); return $method->invokeArgs($object, $parameters); - } catch (\ReflectionException $e) { + } catch (ReflectionException $e) { return null; } } diff --git a/tests/Unit/Models/VoucherStateModelTest.php b/tests/Unit/Models/VoucherStateModelTest.php index 040d40cb3..f3dd99fd1 100644 --- a/tests/Unit/Models/VoucherStateModelTest.php +++ b/tests/Unit/Models/VoucherStateModelTest.php @@ -30,8 +30,8 @@ public function setUp(): void $this->adminUser = factory(AdminUser::class)->create(); } - /** @test */ - public function testProgressVoucherState() + + public function testProgressVoucherState(): void { // We need an auth's user to progress the voucher states. @@ -46,8 +46,8 @@ public function testProgressVoucherState() $this->assertEquals(1, $voucher->history()->count()); } - /** @test */ - public function testTransitionAllowed() + + public function testTransitionAllowed(): void { // We need an auth's user to progress the voucher states. Auth::login($this->marketUser); @@ -61,9 +61,8 @@ public function testTransitionAllowed() } /** - * @test */ - public function testInvalidTransition() + public function testInvalidTransition(): void { $this->expectException(SMException::class); // We need an auth's user to progress the voucher states. @@ -75,19 +74,19 @@ public function testInvalidTransition() $voucher->state('confirm'); } - /** @test */ - public function testAPrintedVoucherCanBeCollected() + + public function testAPrintedVoucherCanBeCollected(): void { Auth::login($this->marketUser); $voucher = factory(Voucher::class)->state('printed')->create(); $voucher->applyTransition('collect'); - $this->assertEquals($voucher->currentstate, 'recorded'); + $this->assertEquals('recorded', $voucher->currentstate); } - /** @test */ - public function testADispatchedVoucherCanBeCollected() + + public function testADispatchedVoucherCanBeCollected(): void { Auth::login($this->marketUser); $voucher = factory(Voucher::class)->state('printed')->create(); @@ -95,15 +94,15 @@ public function testADispatchedVoucherCanBeCollected() $voucher->applyTransition('dispatch'); $voucher->applyTransition('collect'); - $this->assertEquals($voucher->currentstate, 'recorded'); + $this->assertEquals('recorded', $voucher->currentstate); } - /** @test */ - public function testOnlyADispatchedVoucherCanBeExpiredOrVoided() + + public function testOnlyADispatchedVoucherCanBeExpiredOrVoided(): void { Auth::login($this->marketUser); $v = factory(Voucher::class)->state('printed')->create(); - $this->assertEquals($v->currentstate, 'printed'); + $this->assertEquals('printed', $v->currentstate); // Cant get there from printed $this->assertFalse($v->transitionAllowed("expire")); @@ -130,8 +129,8 @@ public function testOnlyADispatchedVoucherCanBeExpiredOrVoided() } } - /** @test */ - public function testAnExpiredOrVoidedVoucherCanBeRetired() + + public function testAnExpiredOrVoidedVoucherCanBeRetired(): void { Auth::login($this->marketUser); $vouchers = factory(Voucher::class, 2)->state('printed') @@ -144,19 +143,19 @@ public function testAnExpiredOrVoidedVoucherCanBeRetired() $v2 = $vouchers->last(); $v1->applyTransition('expire'); - $this->assertEquals($v1->currentstate, 'expired'); + $this->assertEquals('expired', $v1->currentstate); $this->assertTrue($v1->transitionAllowed("retire")); $v1->applyTransition('retire'); - $this->assertEquals($v1->currentstate, 'retired'); + $this->assertEquals('retired', $v1->currentstate); $v2->applyTransition('void'); - $this->assertEquals($v2->currentstate, 'voided'); + $this->assertEquals('voided', $v2->currentstate); $this->assertTrue($v2->transitionAllowed("retire")); $v2->applyTransition('retire'); - $this->assertEquals($v2->currentstate, 'retired'); + $this->assertEquals('retired', $v2->currentstate); } - /** @test */ - public function testARecordedVoucherCanBeRejectedBackToPrinted() + + public function testARecordedVoucherCanBeRejectedBackToPrinted(): void { Auth::login($this->marketUser); $voucher = factory(Voucher::class)->state('printed')->create(); @@ -164,11 +163,11 @@ public function testARecordedVoucherCanBeRejectedBackToPrinted() $voucher->applyTransition('collect'); $voucher->applyTransition('reject-to-printed'); - $this->assertEquals($voucher->currentstate, 'printed'); + $this->assertEquals('printed', $voucher->currentstate); } - /** @test */ - public function testARecordedVoucherCanBeRejectedBackToDispatched() + + public function testARecordedVoucherCanBeRejectedBackToDispatched(): void { Auth::login($this->marketUser); $voucher = factory(Voucher::class)->state('printed')->create(); @@ -177,11 +176,11 @@ public function testARecordedVoucherCanBeRejectedBackToDispatched() $voucher->applyTransition('collect'); $voucher->applyTransition('reject-to-dispatched'); - $this->assertEquals($voucher->currentstate, 'dispatched'); + $this->assertEquals('dispatched', $voucher->currentstate); } - /** @test */ - public function testAVoucherMayHaveAStateToken() + + public function testAVoucherMayHaveAStateToken(): void { // Make a voucher Auth::login($this->marketUser); @@ -193,7 +192,7 @@ public function testAVoucherMayHaveAStateToken() // See it's state doesn't, by default get a state token /** @var VoucherState $state */ $state = $voucher->history->last(); - $this->assertTrue(empty($state->stateToken)); + $this->assertEmpty($state->stateToken); $stateToken = new StateToken(); $stateToken->uuid = "aStringOfCharacters"; @@ -201,12 +200,12 @@ public function testAVoucherMayHaveAStateToken() // Create and associate one $state->stateToken()->associate($stateToken); // See that it has one - $this->assertFalse(empty($state->stateToken)); + $this->assertNotEmpty($state->stateToken); $this->assertEquals("aStringOfCharacters", $state->stateToken->uuid); } - /** @test */ - public function testItCanBatchInsertVoucherStates() + + public function testItCanBatchInsertVoucherStates(): void { // Make a 100 vouchers Auth::login($this->adminUser); diff --git a/tests/Unit/Passport/RoutesTest.php b/tests/Unit/Passport/RoutesTest.php index 12220517b..fd2fdd5ac 100644 --- a/tests/Unit/Passport/RoutesTest.php +++ b/tests/Unit/Passport/RoutesTest.php @@ -32,7 +32,7 @@ protected function setUp(): void ; } - public function testGetAccessTokenWithGoodCredentials() + public function testGetAccessTokenWithGoodCredentials(): void { $this->post('/oauth/token', [ 'grant_type' => 'password', @@ -44,7 +44,7 @@ public function testGetAccessTokenWithGoodCredentials() ])->assertJsonStructure(['access_token', 'refresh_token']); } - public function testDontGetAccessTokenWithBadClientId() + public function testDontGetAccessTokenWithBadClientId(): void { $response = $this->post('/oauth/token', [ 'grant_type' => 'password', @@ -55,14 +55,14 @@ public function testDontGetAccessTokenWithBadClientId() 'scope' => '', ])->getContent(); - $this->assertEquals(json_decode($response, true), [ + $this->assertEquals([ 'error' => 'invalid_client', 'error_description' => 'Client authentication failed', 'message' => 'Client authentication failed', - ]); + ], json_decode($response, true)); } - public function testDontGetAccessTokenWithBadClientSecret() + public function testDontGetAccessTokenWithBadClientSecret(): void { $response = $this->post('/oauth/token', [ 'grant_type' => 'password', @@ -73,14 +73,14 @@ public function testDontGetAccessTokenWithBadClientSecret() 'scope' => '', ])->getContent(); - $this->assertEquals(json_decode($response, true), [ + $this->assertEquals([ 'error' => 'invalid_client', 'error_description' => 'Client authentication failed', 'message' => 'Client authentication failed', - ]); + ], json_decode($response, true)); } - public function testDontGetAccessTokenWithBadUsername() + public function testDontGetAccessTokenWithBadUsername(): void { $response = $this->post('/oauth/token', [ 'grant_type' => 'password', @@ -101,7 +101,7 @@ public function testDontGetAccessTokenWithBadUsername() ); } - public function testDontGetAccessTokenWithBadUserPassword() + public function testDontGetAccessTokenWithBadUserPassword(): void { $response = $this->post('/oauth/token', [ 'grant_type' => 'password', diff --git a/tests/Unit/Providers/MandrillMailServiceProviderTest.php b/tests/Unit/Providers/MandrillMailServiceProviderTest.php index 373a8ef04..f9c7f3b47 100644 --- a/tests/Unit/Providers/MandrillMailServiceProviderTest.php +++ b/tests/Unit/Providers/MandrillMailServiceProviderTest.php @@ -14,14 +14,16 @@ use Symfony\Contracts\HttpClient\ResponseInterface; use Tests\TestCase; use Exception; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; +use PHPUnit\Framework\Attributes\PreserveGlobalState; /** * required to keep overloaded mocks out of other tests * Note, this screws with xdebug, which loses track of processes * Disable these to debug individual tests in this file - * @runTestsInSeparateProcesses - * @preserveGlobalState disabled */ +#[RunTestsInSeparateProcesses] +#[PreserveGlobalState(false)] class MandrillMailServiceProviderTest extends TestCase { protected function setUp(): void @@ -33,15 +35,15 @@ protected function setUp(): void Config::set('services.mandrill.key', 'SomeRandomString'); } - /** @test */ - public function it_extends_the_mail_manager_with_mandrill_driver(): void + + public function testItExtendsTheMailManagerWithMandrillDriver(): void { $driver = Mail::driver('mandrill'); $this->assertInstanceOf(Mailer::class, $driver); } - /** @test */ - public function it_might_try_to_hand_off_to_mandrill(): void + + public function testItMightTryToHandOffToMandrill(): void { // mock the CurlHttpClient the symfony uses to _avoid_ actually contacting mandrill $mockHttpClient = Mockery::mock( diff --git a/tests/Unit/Routes/ApiRoutesTest.php b/tests/Unit/Routes/ApiRoutesTest.php index 6b4618f71..dabb81da0 100644 --- a/tests/Unit/Routes/ApiRoutesTest.php +++ b/tests/Unit/Routes/ApiRoutesTest.php @@ -69,7 +69,7 @@ protected function setUp(): void $this->vouchers[1]->applyTransition('collect'); } - public function testMustHaveAtLeastOneEnabledTraderToLogin() + public function testMustHaveAtLeastOneEnabledTraderToLogin(): void { // create 2 traders and add them to the user $traders = factory(Trader::class, 2)->create(); @@ -97,7 +97,7 @@ public function testMustHaveAtLeastOneEnabledTraderToLogin() ]); } - public function testGetAccessTokenWithGoodCredentials() + public function testGetAccessTokenWithGoodCredentials(): void { $traders = factory(Trader::class)->create(); $this->user->traders()->sync($traders); @@ -109,7 +109,7 @@ public function testGetAccessTokenWithGoodCredentials() $response->assertJsonStructure(['access_token', 'expires_in', 'refresh_token']); } - public function testDontGetAccessTokenWithBadUsername() + public function testDontGetAccessTokenWithBadUsername(): void { $this->post(route('api.login'), [ 'username' => 'nottheusersname@example.com', @@ -121,7 +121,7 @@ public function testDontGetAccessTokenWithBadUsername() ]); } - public function testDontGetAccessTokenWithBadUserPassword() + public function testDontGetAccessTokenWithBadUserPassword(): void { $traders = factory(Trader::class)->create(); $this->user->traders()->sync($traders); @@ -142,7 +142,7 @@ public function testDontGetAccessTokenWithBadUserPassword() } /** REQUIRES AUTH ------------------------------------------------- */ - public function testShowTraderVouchersRoute() + public function testShowTraderVouchersRoute(): void { // This user is not associated with Trader 1. $this->actingAs($this->user, 'api') @@ -158,7 +158,7 @@ public function testShowTraderVouchersRoute() ->assertJsonStructure([0 => ['code', 'updated_at']]) ; } - public function testVouchersRouteHasEtags() + public function testVouchersRouteHasEtags(): void { $trader = factory(Trader::class)->create(); $this->user->traders()->sync([$trader->id]); @@ -168,7 +168,7 @@ public function testVouchersRouteHasEtags() ; } - public function testVouchersRoute304sWithKnownEtag() + public function testVouchersRoute304sWithKnownEtag(): void { $trader = factory(Trader::class)->create(); $this->user->traders()->sync([$trader->id]); @@ -184,7 +184,7 @@ public function testVouchersRoute304sWithKnownEtag() ->assertStatus(304); } - public function testUnauthenticatedDontShowTraderVouchersRoute() + public function testUnauthenticatedDontShowTraderVouchersRoute(): void { $this->json('GET', route('api.trader.vouchers', 1)) ->assertStatus(401) @@ -192,7 +192,7 @@ public function testUnauthenticatedDontShowTraderVouchersRoute() ; } - public function testCollectVoucherRoute() + public function testCollectVoucherRoute(): void { // Get a valid code. $code = $this->vouchers[0]->code; @@ -211,7 +211,7 @@ public function testCollectVoucherRoute() ; } - public function testCollectInvalidVoucherRoute() + public function testCollectInvalidVoucherRoute(): void { // Make up a bogus code. $code = 'BAD88888888'; @@ -230,7 +230,7 @@ public function testCollectInvalidVoucherRoute() ; } - public function testCollectOwnDuplicateVoucherRoute() + public function testCollectOwnDuplicateVoucherRoute(): void { // Get the code already in recorded state. $code = $this->vouchers[1]->code; @@ -253,7 +253,7 @@ public function testCollectOwnDuplicateVoucherRoute() ; } - public function testCollectOtherDuplicateVoucherRoute() + public function testCollectOtherDuplicateVoucherRoute(): void { // Transfer to trader 2 and get the code already in recorded state. $this->vouchers[1]->trader_id = 2; @@ -278,7 +278,7 @@ public function testCollectOtherDuplicateVoucherRoute() ; } - public function testCollectUndeliveredVouchersAfterDeliveriesRoute() + public function testCollectUndeliveredVouchersAfterDeliveriesRoute(): void { $created_at = Carbon::parse(config('arc.first_delivery_date'))->addDay(); $this->vouchers[2]->created_at = $created_at; @@ -302,7 +302,7 @@ public function testCollectUndeliveredVouchersAfterDeliveriesRoute() ; } - public function testCollectUndeliveredVouchersFromBeforeDeliveriesRoute() + public function testCollectUndeliveredVouchersFromBeforeDeliveriesRoute(): void { $created_at = Carbon::parse(config('arc.first_delivery_date'))->subDays(1); $this->vouchers[2]->created_at = $created_at; @@ -324,7 +324,7 @@ public function testCollectUndeliveredVouchersFromBeforeDeliveriesRoute() ; } - public function testRejectToAllocateVoucherRoute() + public function testRejectToAllocateVoucherRoute(): void { // Get a valid code. $code = $this->vouchers[0]->code; @@ -350,7 +350,7 @@ public function testRejectToAllocateVoucherRoute() ; } - public function testUnauthenticatedDontCollectVoucherRoute() + public function testUnauthenticatedDontCollectVoucherRoute(): void { $payload = [ 'transition' => 'collect', @@ -365,7 +365,7 @@ public function testUnauthenticatedDontCollectVoucherRoute() ; } - public function testCantCollectVoucherOnBehalfOfNotOwnTraderRoute() + public function testCantCollectVoucherOnBehalfOfNotOwnTraderRoute(): void { $payload = [ 'transition' => 'collect', @@ -383,7 +383,7 @@ public function testCantCollectVoucherOnBehalfOfNotOwnTraderRoute() ; } - public function testUserCanSeeOwnTraders() + public function testUserCanSeeOwnTraders(): void { $sponsor = factory(Sponsor::class)->create(); $market = factory(Market::class)->create(["sponsor_id" => $sponsor->id]); @@ -402,7 +402,7 @@ function ($t) use ($market) { ; } - public function testUnauthenticatedUserCannotSeeTraders() + public function testUnauthenticatedUserCannotSeeTraders(): void { $this->json('GET', route('api.traders')) ->assertStatus(401) @@ -410,7 +410,7 @@ public function testUnauthenticatedUserCannotSeeTraders() ; } - public function testUserCanSeeOwnTrader() + public function testUserCanSeeOwnTrader(): void { $trader = factory(Trader::class)->create(); $this->user->traders()->sync([$trader->id]); @@ -420,7 +420,7 @@ public function testUserCanSeeOwnTrader() ; } - public function testUserCannotSeeNotOwnTrader() + public function testUserCannotSeeNotOwnTrader(): void { $trader = factory(Trader::class)->create(); // Don't sync this trader to our user. @@ -433,7 +433,7 @@ public function testUserCannotSeeNotOwnTrader() ; } - public function testUnauthenticatedUserCannotSeeTrader() + public function testUnauthenticatedUserCannotSeeTrader(): void { $this->json('GET', route('api.traders')) ->assertStatus(401) @@ -441,7 +441,7 @@ public function testUnauthenticatedUserCannotSeeTrader() ; } - public function testUserCanSeeOwnTraderVoucherHistory() + public function testUserCanSeeOwnTraderVoucherHistory(): void { $trader = factory(Trader::class)->create(); $this->user->traders()->sync([$trader->id]); @@ -451,7 +451,7 @@ public function testUserCanSeeOwnTraderVoucherHistory() ; } - public function testVoucherHistoryHasEtags() + public function testVoucherHistoryHasEtags(): void { $trader = factory(Trader::class)->create(); $this->user->traders()->sync([$trader->id]); @@ -461,7 +461,7 @@ public function testVoucherHistoryHasEtags() ; } - public function testVoucherHistory304sWithKnownEtag() + public function testVoucherHistory304sWithKnownEtag(): void { $trader = factory(Trader::class)->create(); $this->user->traders()->sync([$trader->id]); @@ -477,7 +477,7 @@ public function testVoucherHistory304sWithKnownEtag() ->assertStatus(304); } - public function testUserCannotSeeAnotherTradersVoucherHistory() + public function testUserCannotSeeAnotherTradersVoucherHistory(): void { $trader = factory(Trader::class)->create(); // Don't sync this trader to our user. @@ -490,7 +490,7 @@ public function testUserCannotSeeAnotherTradersVoucherHistory() ; } - public function testUnauthenticatedUserCannotSeeTraderVoucherHistory() + public function testUnauthenticatedUserCannotSeeTraderVoucherHistory(): void { $this->json('GET', route('api.trader.voucher-history', 1)) ->assertStatus(401) diff --git a/tests/Unit/Routes/DataRoutesTest.php b/tests/Unit/Routes/DataRoutesTest.php index 9b07d3650..6d9afb505 100644 --- a/tests/Unit/Routes/DataRoutesTest.php +++ b/tests/Unit/Routes/DataRoutesTest.php @@ -32,14 +32,14 @@ protected function setUp(): void } // These routes are not public. - public function testVouchersIndexRouteNotAuthd() + public function testVouchersIndexRouteNotAuthd(): void { $this->get(route('data.vouchers.index')) ->assertStatus(302); } // Admin Users can see the data. - public function testVouchersIndexRouteAuthdAdmin() + public function testVouchersIndexRouteAuthdAdmin(): void { // Check we don't have timezone info in the dates $this->assertStringNotContainsString('T', $this->vouchers[0]->created_at); @@ -53,7 +53,7 @@ public function testVouchersIndexRouteAuthdAdmin() } // API Users do not have permission. - public function testVouchersIndexRouteAuthdApi() + public function testVouchersIndexRouteAuthdApi(): void { $this->actingAs($this->users[0], 'api') ->get(route('data.vouchers.index')) @@ -61,7 +61,7 @@ public function testVouchersIndexRouteAuthdApi() } // For the rest - we will just be auth'd as admin for sake of time. - public function testVouchersShowRoute() + public function testVouchersShowRoute(): void { $this->actingAs($this->admin, 'admin') ->get(route('data.vouchers.show', $this->vouchers[0])) @@ -71,7 +71,7 @@ public function testVouchersShowRoute() ]); } - public function testUsersIndexRoute() + public function testUsersIndexRoute(): void { // Check we don't have timezone info in the dates $this->assertStringNotContainsString('T', $this->users[0]->created_at); @@ -84,7 +84,7 @@ public function testUsersIndexRoute() ]]); } - public function testMarketsIndexRoute() + public function testMarketsIndexRoute(): void { // Check we don't have timezone info in the dates $this->assertStringNotContainsString('T', $this->markets[0]->created_at); @@ -97,7 +97,7 @@ public function testMarketsIndexRoute() ]]); } - public function testTradersIndexRoute() + public function testTradersIndexRoute(): void { // Check we don't have timezone info in the dates $this->assertStringNotContainsString('T', $this->traders[0]->created_at); @@ -111,7 +111,7 @@ public function testTradersIndexRoute() } // Need to be auth'd for these too. - public function testProductionRoutes() + public function testProductionRoutes(): void { Config::set('app.url', 'https://voucher-admin.alexandrarose.org.uk'); $models = ['vouchers', 'users', 'markets', 'traders']; diff --git a/tests/Unit/Routes/ServiceRoutesTest.php b/tests/Unit/Routes/ServiceRoutesTest.php index 712339ffe..ac1f2da70 100644 --- a/tests/Unit/Routes/ServiceRoutesTest.php +++ b/tests/Unit/Routes/ServiceRoutesTest.php @@ -84,8 +84,8 @@ public function setUp(): void $this->trader = factory(Trader::class)->create(['market_id' => $this->market->id]); } - /** @test */ - public function testServiceLogoutRoute() + + public function testServiceLogoutRoute(): void { $this->actingAs($this->adminUser, 'admin') ->post(route('admin.logout')) @@ -93,15 +93,15 @@ public function testServiceLogoutRoute() ->seeRouteIs('admin.login'); } - /** @test */ - public function testServiceLoginPageRoute() + + public function testServiceLoginPageRoute(): void { $this->get(route('admin.login')) ->assertResponseStatus(200); } - /** @test */ - public function testRouteGates() + + public function testRouteGates(): void { $loginRoute = route('admin.login'); @@ -127,7 +127,7 @@ public function testRouteGates() // And it's not 403, 404, 500, or a redirect-to-login. $this->assertFalse($response->isNotFound()); $this->assertFalse($response->isForbidden()); - $this->assertFalse($this->currentUri === $loginRoute); + $this->assertNotSame($this->currentUri, $loginRoute); $this->assertFalse($response->isServerError()); $this->assertTrue( $response->isOK() diff --git a/tests/Unit/Routes/StoreRoutesTest.php b/tests/Unit/Routes/StoreRoutesTest.php index 131e29ee1..dcdeef642 100644 --- a/tests/Unit/Routes/StoreRoutesTest.php +++ b/tests/Unit/Routes/StoreRoutesTest.php @@ -8,6 +8,7 @@ use App\CentreUser; use App\Registration; use App\Sponsor; +use Exception; use Illuminate\Foundation\Testing\RefreshDatabase; use URL; use Tests\StoreTestCase; @@ -103,9 +104,8 @@ public function setUp(): void * Verify login direct. * * @return void - * @test */ - public function testLoginGuestRoute() + public function testLoginGuestRoute(): void { Auth::logout(); $this->get(URL::route('store.login')) @@ -117,9 +117,8 @@ public function testLoginGuestRoute() * Verify forgot password direct * * @return void - * @test */ - public function testForgotPasswordGuestRoute() + public function testForgotPasswordGuestRoute(): void { Auth::logout(); $this->get(URL::route('store.password.request')) @@ -127,8 +126,8 @@ public function testForgotPasswordGuestRoute() ->assertResponseStatus(200); } - /** @test */ - public function testDashboardRouteGate() + + public function testDashboardRouteGate(): void { Auth::logout(); // You cannot get there logged out. @@ -142,8 +141,8 @@ public function testDashboardRouteGate() ->assertResponseStatus(200); } - /** @test */ - public function testSearchRouteGate() + + public function testSearchRouteGate(): void { $route = URL::route('store.registration.index'); @@ -159,8 +158,8 @@ public function testSearchRouteGate() ->assertResponseStatus(200); } - /** @test */ - public function testEditRouteGate() + + public function testEditRouteGate(): void { // Create a random registration with our centre. $registration = factory(Registration::class)->create([ @@ -187,8 +186,8 @@ public function testEditRouteGate() ->assertResponseStatus(200); } - /** @test */ - public function testUpdateRouteGate() + + public function testUpdateRouteGate(): void { // Create a random registration with our centre. $registration = factory(Registration::class)->create([ @@ -219,8 +218,8 @@ public function testUpdateRouteGate() ->assertResponseStatus(200); } - /** @test */ - public function testVoucherManageRouteGate() + + public function testVoucherManageRouteGate(): void { // Create a random registration with our centre. $registration = factory(Registration::class)->create([ @@ -263,13 +262,13 @@ public function testVoucherManageRouteGate() try { $this->actingAs($this->unrelatedUser, 'store') ->visit($route); - } catch (\Exception $e) { + } catch (Exception $e) { $this->assertStringContainsString('Received status code [403]', $e->getMessage()); } } - /** @test */ - public function testVoucherManagerUpdateGate() + + public function testVoucherManagerUpdateGate(): void { // Create a random registration with our centre. $registration = factory(Registration::class)->create([ @@ -316,13 +315,12 @@ public function testVoucherManagerUpdateGate() $put_route, [] // should erase the vouchers. ); - } catch (\Exception $e) { + } catch (Exception $e) { $this->assertStringContainsString('Received status code [403]', $e->getMessage()); } } - /** test */ - public function testCentresRegistrationsSummaryGate() + public function testCentresRegistrationsSummaryGate(): void { // Make some registrations factory(Registration::class, 5)->create([ @@ -360,8 +358,7 @@ public function testCentresRegistrationsSummaryGate() ->assertResponseOK(); } - /** test */ - public function testCentreRegistrationsSummaryGate() + public function testCentreRegistrationsSummaryGate(): void { // Make some registrations factory(Registration::class, 5)->create([ @@ -400,8 +397,8 @@ public function testCentreRegistrationsSummaryGate() ->assertResponseOK(); } - /** @test */ - public function testSessionUpdateGate() + + public function testSessionUpdateGate(): void { $put_route = URL::route('store.session.put'); @@ -430,8 +427,8 @@ public function testSessionUpdateGate() ->assertResponseStatus(200); } - /** @test */ - public function testRegistrationFamilyUpdateGate() + + public function testRegistrationFamilyUpdateGate(): void { $registration = factory(Registration::class)->create([ 'centre_id' => $this->centre->id, @@ -484,13 +481,13 @@ public function testRegistrationFamilyUpdateGate() $route, ['leaving_reason' => 'found employment'] ); - } catch (\Exception $e) { + } catch (Exception $e) { $this->assertStringContainsString('Received status code [403]', $e->getMessage()); } } - /** @test */ - public function testMVLRouteGuard() + + public function testMVLRouteGuard(): void { // Make some registrations factory(Registration::class, 5)->create([ @@ -533,8 +530,8 @@ public function testMVLRouteGuard() ->assertResponseOK(); } - /** @test */ - public function testCentreRegistrationCollectionGate() + + public function testCentreRegistrationCollectionGate(): void { factory(Registration::class, 5)->create([ 'centre_id' => $this->centre->id, diff --git a/tests/Unit/Rules/NotExistsRuleTest.php b/tests/Unit/Rules/NotExistsRuleTest.php index c3be39abb..ec92e67b7 100644 --- a/tests/Unit/Rules/NotExistsRuleTest.php +++ b/tests/Unit/Rules/NotExistsRuleTest.php @@ -11,8 +11,8 @@ class NotExistsRuleTest extends TestCase { use RefreshDatabase; - /** @test */ - public function theNotExistsRuleValidates() + + public function testTheNotExistsRuleValidates(): void { // Create a rules set. $rule = [ @@ -26,7 +26,7 @@ public function theNotExistsRuleValidates() $user = factory(User::class)->create(); // Succeed at failing to find a user id - $this->assertTrue(validator(['user_id' => $user->id +1 ], $rule)->passes()); + $this->assertTrue(validator(['user_id' => $user->id + 1 ], $rule)->passes()); // Fail at failing to find a user id $this->assertFalse(validator(['user_id' => $user->id], $rule)->passes()); diff --git a/tests/Unit/Services/VoucherEvaluator/SPVoucherEvaluatorTest.php b/tests/Unit/Services/VoucherEvaluator/SPVoucherEvaluatorTest.php index 0ed5f7218..3615294c2 100644 --- a/tests/Unit/Services/VoucherEvaluator/SPVoucherEvaluatorTest.php +++ b/tests/Unit/Services/VoucherEvaluator/SPVoucherEvaluatorTest.php @@ -18,7 +18,7 @@ class SPVoucherEvaluatorTest extends TestCase { use RefreshDatabase; - const CREDIT_TYPES = [ + public const CREDIT_TYPES = [ 'HouseholdExists' => ['reason' => 'Family|exists', 'value' => 10], 'HouseholdMember' => ['reason' => 'Child|member of the household', 'value' => 7], 'DeductFromCarer' => ['reason' => 'Family|', 'value' => -7], @@ -80,13 +80,13 @@ protected function setUp(): void ]), new Evaluation([ "name" => "ChildIsAlmostPrimarySchoolAge", - "value" => NULL, + "value" => null, "purpose" => "notices", "entity" => "App\Child", ]), new Evaluation([ "name" => "ChildIsAlmostOne", - "value" => NULL, + "value" => null, "purpose" => "notices", "entity" => "App\Child", ]), @@ -114,20 +114,20 @@ protected function setUp(): void $this->isAlmostOne = factory(Child::class)->state('almostOne')->make(); } - /** @test */ - public function itCreditsWhenAHouseholdExists() + + public function testItCreditsWhenAHouseholdExists(): void { $rulesMods = collect($this->rulesMods["credit-sp"]); $evaluator = EvaluatorFactory::make($rulesMods); $evaluation = $evaluator->evaluate($this->family); $credits = $evaluation["credits"]; - $this->assertEquals(2, count($credits)); + $this->assertCount(2, $credits); $this->assertContains(self::CREDIT_TYPES['HouseholdExists'], $credits); $this->assertEquals('10', $evaluation->getEntitlement()); } - /** @test */ - public function itCreditsWhenAHouseholdMemberExists() + + public function testItCreditsWhenAHouseholdMemberExists(): void { $this->family->children()->save($this->isPrimarySchool); $rulesMods = collect($this->rulesMods["credit-sp"]); @@ -137,8 +137,8 @@ public function itCreditsWhenAHouseholdMemberExists() $this->assertEquals('17', $evaluation->getEntitlement()); } - /** @test */ - public function itCreditsWhenMultipleHouseholdMembersExist() + + public function testItCreditsWhenMultipleHouseholdMembersExist(): void { $this->family->children()->saveMany([$this->isPrimarySchool, $this->underOne]); $rulesMods = collect($this->rulesMods["credit-sp"]); @@ -147,8 +147,8 @@ public function itCreditsWhenMultipleHouseholdMembersExist() $this->assertEquals('24', $evaluation->getEntitlement()); } - /** @test */ - public function socialPrescriptionUsersDontSeeNoticesForPrimary() + + public function testSocialPrescriptionUsersDontSeeNoticesForPrimary(): void { Config::set('arc.school_month', Carbon::now()->addMonth()->month); $this->family->children()->save($this->readyForPrimarySchool); @@ -156,17 +156,17 @@ public function socialPrescriptionUsersDontSeeNoticesForPrimary() $evaluator = EvaluatorFactory::make($rulesMods); $evaluation = $evaluator->evaluate($this->family); $notices = $evaluation["notices"]; - $this->assertEquals(0, count($notices)); + $this->assertCount(0, $notices); } - /** @test */ - public function socialPrescriptionUsersDontSeeNoticesForChildIsAlmostOne() + + public function testSocialPrescriptionUsersDontSeeNoticesForChildIsAlmostOne(): void { $this->family->children()->save($this->isAlmostOne); $rulesMods = collect($this->rulesMods["credit-sp"]); $evaluator = EvaluatorFactory::make($rulesMods); $evaluation = $evaluator->evaluate($this->family); $notices = $evaluation["notices"]; - $this->assertEquals(0, count($notices)); + $this->assertCount(0, $notices); } } diff --git a/tests/Unit/Services/VoucherEvaluator/ScottishVoucherEvaluatorTest.php b/tests/Unit/Services/VoucherEvaluator/ScottishVoucherEvaluatorTest.php index 59759210c..600010d22 100644 --- a/tests/Unit/Services/VoucherEvaluator/ScottishVoucherEvaluatorTest.php +++ b/tests/Unit/Services/VoucherEvaluator/ScottishVoucherEvaluatorTest.php @@ -19,7 +19,7 @@ class ScottishVoucherEvaluatorTest extends TestCase use RefreshDatabase; // This has a | in the reason field because we want to carry the entity with it. - const NOTICE_TYPES = [ + public const NOTICE_TYPES = [ 'ChildIsAlmostOne' => ['reason' => 'Child|almost 1 year old'], 'ScottishChildIsAlmostPrimarySchoolAge' => ['reason' => 'Child|almost primary school age (SCOTLAND)'], 'ChildIsAlmostSecondarySchoolAge' => ['reason' => 'Child|almost secondary school age'], @@ -30,7 +30,7 @@ class ScottishVoucherEvaluatorTest extends TestCase ]; // This has a | in the reason field because we want to carry the entity with it. - const CREDIT_TYPES = [ + public const CREDIT_TYPES = [ 'ChildIsUnderOne' => ['reason' => 'Child|under 1 year old', 'value' => 6], 'ScottishChildIsBetweenOneAndPrimarySchoolAge' => ['reason' => 'Child|between 1 and start of primary school age (SCOTLAND)', 'value' => 4], 'ChildIsPrimarySchoolAge' => ['reason' => 'Child|primary school age', 'value' => 4], @@ -156,8 +156,8 @@ protected function setUp(): void $this->canNotDefer = factory(Child::class)->state('canNotDefer')->make(); } - /** @test */ - public function itCreditsWhenAFamilyIsPregnant(): void + + public function testItCreditsWhenAFamilyIsPregnant(): void { $this->family->children()->save($this->pregnancy); @@ -168,12 +168,12 @@ public function itCreditsWhenAFamilyIsPregnant(): void $credits = $evaluation["credits"]; // There should be a credit reason of 'FamilyIsPregnant' - $this->assertEquals(1, count($credits)); + $this->assertCount(1, $credits); $this->assertContains(self::CREDIT_TYPES['FamilyIsPregnant'], $credits); } - /** @test */ - public function itDoesntCreditPrimarySchoolChildren(): void + + public function testItDoesntCreditPrimarySchoolChildren(): void { // get rules mods $rulesMods = collect($this->rulesMods["credit-primary"]); @@ -192,8 +192,8 @@ public function itDoesntCreditPrimarySchoolChildren(): void $this->assertEquals('0', $evaluation->getEntitlement()); } - /** @test */ - public function itCreditsQualifiedPrimarySchoolChildrenButNotUnqualifiedOnes(): void + + public function testItCreditsQualifiedPrimarySchoolChildrenButNotUnqualifiedOnes(): void { $rulesMods = collect($this->rulesMods["credit-primary"]); // Make evaluator @@ -230,8 +230,8 @@ public function itCreditsQualifiedPrimarySchoolChildrenButNotUnqualifiedOnes(): $this->assertEquals('8', $evaluation->getEntitlement()); } - /** @test */ - public function itCreditsWhenAChildIsBetweenOneAndPrimarySchoolAge() + + public function testItCreditsWhenAChildIsBetweenOneAndPrimarySchoolAge(): void { $rulesMods = collect($this->rulesMods["credit-primary"]); // Make evaluator @@ -240,7 +240,7 @@ public function itCreditsWhenAChildIsBetweenOneAndPrimarySchoolAge() $credits = $evaluation["credits"]; // Check there's one, because child is not under one. - $this->assertEquals(1, count($credits)); + $this->assertCount(1, $credits); // Check the correct credit type is applied. $this->assertContains(self::CREDIT_TYPES['ScottishChildIsBetweenOneAndPrimarySchoolAge'], $credits); @@ -248,8 +248,8 @@ public function itCreditsWhenAChildIsBetweenOneAndPrimarySchoolAge() } - /** @test */ - public function itNoticesWhenAChildIsAlmostPrimarySchoolAge() + + public function testItNoticesWhenAChildIsAlmostPrimarySchoolAge(): void { $this->markTestSkipped('Waiting for hotfix'); // Need to change the values we use for school start to next month's integer @@ -261,18 +261,18 @@ public function itNoticesWhenAChildIsAlmostPrimarySchoolAge() $notices = $evaluation["notices"]; // Check there's one, because no other event is pending. - $this->assertEquals(1, count($notices)); + $this->assertCount(1, $notices); // Check the correct credit type is applied. $this->assertNotContains(self::NOTICE_TYPES['ChildIsAlmostOne'], $notices); $this->assertContains(self::NOTICE_TYPES['ScottishChildIsAlmostPrimarySchoolAge'], $notices); } - /** @test */ + // the scottish deferral code doesn't like december dates. // this is probably a bug in the Evaluator specification // not dealing with a year-wrapping check - public function itNoticesWhenAChildCanDefer(): void + public function testItNoticesWhenAChildCanDefer(): void { $this->markTestSkipped('Waiting for hotfix'); // Need to change the values we use for school start to next month's integer @@ -284,7 +284,7 @@ public function itNoticesWhenAChildCanDefer(): void $notices = $evaluation["notices"]; // Check there's one - $this->assertEquals(2, count($notices)); + $this->assertCount(2, $notices); // Check the correct credit type is applied. $this->assertNotContains(self::NOTICE_TYPES['ChildIsAlmostOne'], $notices); diff --git a/tests/Unit/Services/VoucherEvaluator/VoucherEvaluatorTest.php b/tests/Unit/Services/VoucherEvaluator/VoucherEvaluatorTest.php index 0bf61250c..a83a687f7 100644 --- a/tests/Unit/Services/VoucherEvaluator/VoucherEvaluatorTest.php +++ b/tests/Unit/Services/VoucherEvaluator/VoucherEvaluatorTest.php @@ -126,8 +126,8 @@ protected function setUp(): void $this->readyForSecondarySchool = factory(Child::class)->state('readyForSecondarySchool')->make(); } - /** @test */ - public function itNoticesWhenAFamilyStillRequiresIDForChildren(): void + + public function testItNoticesWhenAFamilyStillRequiresIDForChildren(): void { $unverifiedKids = factory(Child::class, 3)->states('unverified')->make(); $this->family->children()->saveMany($unverifiedKids); @@ -159,8 +159,8 @@ public function itNoticesWhenAFamilyStillRequiresIDForChildren(): void $this->assertNotContains(self::NOTICE_TYPES['FamilyHasUnverifiedChildren'], $notices); } - /** @test */ - public function itCreditsWhenAFamilyIsPregnant(): void + + public function testItCreditsWhenAFamilyIsPregnant(): void { $this->family->children()->save($this->pregnancy); @@ -174,8 +174,8 @@ public function itCreditsWhenAFamilyIsPregnant(): void $this->assertContains(self::CREDIT_TYPES['FamilyIsPregnant'], $credits); } - /** @test */ - public function itCreditsUnrestrictedPrimarySchoolChildren(): void + + public function testItCreditsUnrestrictedPrimarySchoolChildren(): void { // get rules mods $rulesMods = collect($this->rulesMods["credit-primary"]); @@ -195,8 +195,8 @@ public function itCreditsUnrestrictedPrimarySchoolChildren(): void $this->assertEquals('4', $evaluation->getEntitlement()); } - /** @test */ - public function itCreditsQualifiedPrimarySchoolChildrenButNotUnqualifiedOnes(): void + + public function testItCreditsQualifiedPrimarySchoolChildrenButNotUnqualifiedOnes(): void { // get rules mods $rulesMods = collect($this->rulesMods["credit-primary-qualified"]); @@ -235,8 +235,8 @@ public function itCreditsQualifiedPrimarySchoolChildrenButNotUnqualifiedOnes(): $this->assertEquals('8', $evaluation->getEntitlement()); } - /** @test */ - public function itCreditsWhenAChildIsUnderOne(): void + + public function testItCreditsWhenAChildIsUnderOne(): void { // Make standard evaluator for a child under one $evaluator = EvaluatorFactory::make(); @@ -250,8 +250,8 @@ public function itCreditsWhenAChildIsUnderOne(): void $this->assertEquals(6, $evaluation->getEntitlement()); } - /** @test */ - public function itCreditsWhenAChildIsBetweenOneAndPrimarySchoolAge(): void + + public function testItCreditsWhenAChildIsBetweenOneAndPrimarySchoolAge(): void { // Make standard evaluator for a child under school age $evaluator = EvaluatorFactory::make(); @@ -266,8 +266,8 @@ public function itCreditsWhenAChildIsBetweenOneAndPrimarySchoolAge(): void $this->assertEquals(4, $evaluation->getEntitlement()); } - /** @test */ - public function itDoesNotCreditWhenAChildisSecondarySchoolAge(): void + + public function testItDoesNotCreditWhenAChildisSecondarySchoolAge(): void { $rulesMod = collect($this->rulesMods["credit-primary"]); @@ -281,8 +281,8 @@ public function itDoesNotCreditWhenAChildisSecondarySchoolAge(): void $this->assertEquals(0, $evaluation->getEntitlement()); } - /** @test */ - public function itNoticesWhenAChildIsAlmostOne(): void + + public function testItNoticesWhenAChildIsAlmostOne(): void { // Make standard evaluator $evaluator = EvaluatorFactory::make(); @@ -297,8 +297,8 @@ public function itNoticesWhenAChildIsAlmostOne(): void $this->assertNotContains(self::NOTICE_TYPES['ChildIsAlmostPrimarySchoolAge'], $notices); } - /** @test */ - public function itNoticesWhenAChildIsAlmostPrimarySchoolAge(): void + + public function testItNoticesWhenAChildIsAlmostPrimarySchoolAge(): void { // Need to change the values we use for school start to next month's integer Config::set('arc.school_month', Carbon::now()->addMonthsNoOverflow(1)->month); @@ -316,8 +316,8 @@ public function itNoticesWhenAChildIsAlmostPrimarySchoolAge(): void $this->assertContains(self::NOTICE_TYPES['ChildIsAlmostPrimarySchoolAge'], $notices); } - /** @test */ - public function itNoticesWhenAChildIsAlmostSecondarySchoolAge(): void + + public function testItNoticesWhenAChildIsAlmostSecondarySchoolAge(): void { // Need to change the values we use for school start to next month's integer Config::set('arc.school_month', Carbon::now()->addMonthsNoOverflow(1)->month); @@ -338,8 +338,8 @@ public function itNoticesWhenAChildIsAlmostSecondarySchoolAge(): void $this->assertContains(self::NOTICE_TYPES['ChildIsAlmostSecondarySchoolAge'], $notices); } - /** @test */ - public function roundingUpAgeToEndOfMonth(): void + + public function testRoundingUpAgeToEndOfMonth(): void { // Create a child with a DOB of 12th April 2000 $dob = Carbon::create(2000, 4, 12, 0, 0, 0, 'Europe/London'); @@ -368,8 +368,8 @@ public function roundingUpAgeToEndOfMonth(): void } } - /** @test */ - public function itHasADefaultSetOfRulesAndCanAcceptVariableValuesForEvaluations(): void + + public function testItHasADefaultSetOfRulesAndCanAcceptVariableValuesForEvaluations(): void { // We make a registration in a non-SK area $centre = factory(Centre::class)->create(); diff --git a/tests/Unit/Traits/AliasableTraitTest.php b/tests/Unit/Traits/AliasableTraitTest.php index 78d1dc198..8cb265c9e 100644 --- a/tests/Unit/Traits/AliasableTraitTest.php +++ b/tests/Unit/Traits/AliasableTraitTest.php @@ -16,7 +16,7 @@ public function setUp(): void { parent::setUp(); - $this->UnaliasedTraitClassObject = new class extends Model { + $this->UnaliasedTraitClassObject = new class () extends Model { use Aliasable; /** @@ -27,7 +27,7 @@ public function setUp(): void }; - $this->AliasedTraitClassObject = new class extends Model { + $this->AliasedTraitClassObject = new class () extends Model { use Aliasable; /** @@ -36,11 +36,10 @@ public function setUp(): void */ public const PROGRAMME_ALIASES = ['first', 'second']; }; - } - /** @test */ - public function itCanSupplyAnAlias() + + public function testItCanSupplyAnAlias(): void { // function exists and returns a string $entity = $this->UnaliasedTraitClassObject; From 59090c37554bdeb1a6119b4bdc5e59fa50c11522 Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 24 Mar 2026 16:12:02 +0000 Subject: [PATCH 015/168] feat: update commands, refactor add tests --- .../Commands/MoveFamilyRegistrationCentre.php | 100 +++---- app/Console/Commands/PurgeFamilyGraph.php | 83 ++---- app/Family.php | 35 +++ .../Service/Admin/SponsorsController.php | 2 +- storage/app/.gitignore | 3 - storage/app/local/.keep | 0 storage/app/public/.gitignore | 2 - storage/framework/testing/.gitignore | 2 - .../MoveFamilyRegistrationCentreTest.php | 264 ++++++++++++++++++ .../Console/Commands/PurgeFamilyGraphTest.php | 251 +++++++++++++++++ tests/Feature/Service/LoggingTest.php | 16 +- tests/Unit/Models/FamilyModelTest.php | 35 ++- 12 files changed, 658 insertions(+), 135 deletions(-) delete mode 100755 storage/app/.gitignore delete mode 100644 storage/app/local/.keep delete mode 100755 storage/app/public/.gitignore delete mode 100755 storage/framework/testing/.gitignore create mode 100644 tests/Console/Commands/MoveFamilyRegistrationCentreTest.php create mode 100644 tests/Console/Commands/PurgeFamilyGraphTest.php diff --git a/app/Console/Commands/MoveFamilyRegistrationCentre.php b/app/Console/Commands/MoveFamilyRegistrationCentre.php index f60d48ef5..6e05dccd5 100644 --- a/app/Console/Commands/MoveFamilyRegistrationCentre.php +++ b/app/Console/Commands/MoveFamilyRegistrationCentre.php @@ -8,6 +8,7 @@ use Illuminate\Console\Command; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Storage; use RuntimeException; use Throwable; @@ -32,7 +33,8 @@ public function handle(): int if ( ($csvPath && ($centreId || $familyId)) || - (!$csvPath && !($centreId && $familyId))) { + (!$csvPath && !($centreId && $familyId)) + ) { $this->error('Provide either a family_id and centre_id OR --csv=path'); return self::FAILURE; } @@ -44,8 +46,8 @@ public function handle(): int } if ($csvPath) { - if (!file_exists($csvPath)) { - $this->error("CSV file not found: {$csvPath}"); + if (!Storage::exists($csvPath)) { + $this->error("CSV file not found: $csvPath"); return self::FAILURE; } @@ -68,7 +70,7 @@ function ($item) { foreach ($workList as $item) { ['familyId' => $familyId, 'centreId' => $centreId] = $item; $this->line(''); - $this->line("==== FAMILY {$familyId} ===="); + $this->line("==== FAMILY $familyId ===="); $result = $this->moveSingleFamily($familyId, $centreId, $dryRun, $force); @@ -86,10 +88,10 @@ function ($item) { return self::FAILURE; } - $path = "/tmp/movesDone.csv"; + $path = "movesDone.csv"; if (count($this->summary) > 0) { $this->writeCsv($path, $this->summary); - $this->line("Wrote changes to {$path}"); + $this->line("Wrote changes to $path"); } return self::SUCCESS; @@ -99,8 +101,10 @@ private function extractFromCsv(string $path): Collection { $pairs = collect(); - if (($handle = fopen($path, 'rb')) === false) { - throw new RuntimeException("Cannot open CSV: {$path}"); + $handle = Storage::readStream($path); + + if ($handle === false) { + throw new RuntimeException("Cannot open CSV: $path"); } $header = fgetcsv($handle); @@ -126,25 +130,27 @@ private function extractFromCsv(string $path): Collection $centreMap = Centre::query()->pluck('id', 'name'); while (($row = fgetcsv($handle)) !== false) { - $rvid = trim((string)($row[$rvidIndex] ?? '')); $centreName = trim((string)($row[$centreIndex] ?? '')); - $family = self::findByRvid($rvid); + $family = Family::findByRvid($rvid); if (!$family) { - $this->line("Invalid RVID: {$rvid}"); + $this->line("Invalid RVID: $rvid"); continue; } $centreId = $centreMap[$centreName] ?? null; if (!$centreId) { - $this->line("Invalid Centre: {$centreName}"); + $this->line("Invalid Centre: $centreName"); continue; } - $pairs->push(['familyId' => $family->id, 'centreId' => $centreId]); + $pairs->push([ + 'familyId' => $family->id, + 'centreId' => $centreId, + ]); } fclose($handle); @@ -152,52 +158,17 @@ private function extractFromCsv(string $path): Collection return $pairs; } - public static function findByRvid(string $rvid): ?Family - { - $rvid = strtoupper(trim($rvid)); - - if ($rvid === '') { - return null; - } - - // IMPORTANT: longest prefix first (prevents AB matching before AB1) - $centres = Centre::query() - ->select('id', 'prefix') - ->orderByRaw('LENGTH(prefix) DESC') - ->get()->all(); - - foreach ($centres as $centre) { - if (!str_starts_with($rvid, $centre->prefix)) { - continue; - } - $sequencePart = substr($rvid, strlen($centre->prefix)); - - if (!ctype_digit($sequencePart)) { - continue; - } - - $sequence = (int)$sequencePart; - - return Family::query() - ->where('initial_centre_id', $centre->id) - ->where('centre_sequence', $sequence) - ->first(); - } - - return null; - } - private function moveSingleFamily(int $familyId, int $centreId, bool $dryRun, bool $force): int { $centre = Centre::find($centreId); if (!$centre) { - $this->error("Centre {$centreId} not found."); + $this->error("Centre $centreId not found."); return self::FAILURE; } $family = Family::withPrimaryCarer()->whereKey($familyId)->lockForUpdate()->first(); if (!$family) { - $this->error("Family {$familyId} not found."); + $this->error("Family $familyId not found."); return self::FAILURE; } @@ -211,11 +182,11 @@ private function moveSingleFamily(int $familyId, int $centreId, bool $dryRun, bo $centreNames = $registrations->pluck('centre.name')->all(); $centreNames = implode(", ", array_unique(array_sort($centreNames))); - $this->info("Family: {$family->id} ({$family->rvid})"); - $this->info("Primary Carer: {$family->pri_carer}"); + $this->info("Family: $family->id ($family->rvid)"); + $this->info("Primary Carer: $family->pri_carer"); $this->line("Registrations: {$registrations->count()}"); - $this->line("In Centres: {$centreNames}"); - $this->line("Move to Centre: {$centre->id} ({$centre->name})"); + $this->line("In Centres: $centreNames"); + $this->line("Move to Centre: $centre->id ($centre->name)"); if ($dryRun) { $this->warn('Dry run complete — nothing deleted.'); @@ -224,7 +195,7 @@ private function moveSingleFamily(int $familyId, int $centreId, bool $dryRun, bo if ( !$force && !$this->confirm( - "This will permanently move family {$family->id} and related data to {$centre->name}. Continue?" + "This will permanently move family $family->id and related data to $centre->name. Continue?" ) ) { $this->warn('Skipped'); @@ -263,20 +234,27 @@ private function moveRegistrations(Collection $registrations, $centre): void if ($actioned !== $expected) { throw new RuntimeException( - "Registration move mismatch. Expected {$expected}, moved {$actioned}." + "Registration move mismatch. Expected $expected, moved $actioned." ); } } private function writeCsv(string $path, iterable $rows): void { - $handle = fopen($path, 'wb'); - if ($handle === false) { - throw new RuntimeException("Cannot write CSV to: {$path}"); + $stream = fopen('php://temp', 'w+'); + + if ($stream === false) { + throw new RuntimeException('Cannot open temp stream.'); } + foreach ($rows as $row) { - fputcsv($handle, $row); + fputcsv($stream, $row); } - fclose($handle); + + rewind($stream); + + Storage::put($path, stream_get_contents($stream)); + + fclose($stream); } } diff --git a/app/Console/Commands/PurgeFamilyGraph.php b/app/Console/Commands/PurgeFamilyGraph.php index 68075bd84..669be1e92 100644 --- a/app/Console/Commands/PurgeFamilyGraph.php +++ b/app/Console/Commands/PurgeFamilyGraph.php @@ -4,7 +4,6 @@ use App\Bundle; use App\Carer; -use App\Centre; use App\Child; use App\Family; use App\Registration; @@ -12,6 +11,7 @@ use Illuminate\Console\Command; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Storage; use RuntimeException; use Throwable; @@ -44,13 +44,13 @@ public function handle(): int } if ($csvPath) { - if (!file_exists($csvPath)) { - $this->error("CSV file not found: {$csvPath}"); + if (!Storage::exists($csvPath)) { + $this->error("CSV file not found: $csvPath"); return self::FAILURE; } $familyIds = $familyIds->merge( - $this->extractFamilyIdsFromCsv($csvPath) + $this->extractFromCsv($csvPath) ); } @@ -62,7 +62,7 @@ public function handle(): int foreach ($familyIds as $id) { $this->line(''); - $this->line("==== FAMILY {$id} ===="); + $this->line("==== FAMILY $id ===="); $result = $this->purgeSingleFamily($id, $dryRun, $force); @@ -83,12 +83,14 @@ public function handle(): int return self::SUCCESS; } - private function extractFamilyIdsFromCsv(string $path): Collection + private function extractFromCsv(string $path): Collection { $ids = collect(); - if (($handle = fopen($path, 'rb')) === false) { - throw new RuntimeException("Cannot open CSV: {$path}"); + $handle = Storage::readStream($path); + + if ($handle === false) { + throw new RuntimeException("Cannot open CSV: $path"); } $header = fgetcsv($handle); @@ -106,11 +108,11 @@ private function extractFamilyIdsFromCsv(string $path): Collection } while (($row = fgetcsv($handle)) !== false) { - $family = self::findByRvid($row[$rvid]); + $family = Family::findByRvid($row[$rvid]); if ($family !== null) { $ids->push($family->id); } else { - $this->line("Invalid rvid: {$row[$rvid]}"); + $this->line("Invalid rvid: $row[$rvid]"); } } @@ -118,50 +120,15 @@ private function extractFamilyIdsFromCsv(string $path): Collection return $ids; } - public static function findByRvid(string $rvid): ?Family - { - $rvid = strtoupper(trim($rvid)); - - if ($rvid === '') { - return null; - } - - // IMPORTANT: longest prefix first (prevents AB matching before AB1) - $centres = Centre::query() - ->select('id', 'prefix') - ->orderByRaw('LENGTH(prefix) DESC') - ->get()->all(); - - foreach ($centres as $centre) { - if (!str_starts_with($rvid, $centre->prefix)) { - continue; - } - $sequencePart = substr($rvid, strlen($centre->prefix)); - - if (!ctype_digit($sequencePart)) { - continue; - } - - $sequence = (int)$sequencePart; - - return Family::query() - ->where('initial_centre_id', $centre->id) - ->where('centre_sequence', $sequence) - ->first(); - } - - return null; - } - private function purgeSingleFamily(int $familyId, bool $dryRun, bool $force): int { try { - return DB::transaction(callback: function () use ($familyId, $dryRun, $force) { + return DB::transaction(callback: function() use ($familyId, $dryRun, $force) { $family = Family::withPrimaryCarer()->whereKey($familyId)->lockForUpdate()->first(); if (!$family) { - $this->error("Family {$familyId} not found."); + $this->error("Family $familyId not found."); return self::FAILURE; } @@ -178,13 +145,13 @@ private function purgeSingleFamily(int $familyId, bool $dryRun, bool $force): in $childrenCount = Child::where('family_id', $familyId)->count(); $carersCount = Carer::where('family_id', $familyId)->withTrashed()->count(); - $this->info("Family: {$familyId}"); - $this->info("Primary Carer: {$family->pri_carer}"); + $this->info("Family: $familyId"); + $this->info("Primary Carer: $family->pri_carer"); $this->line("Registrations: {$registrationIds->count()}"); $this->line("Bundles: {$bundleIds->count()}"); - $this->line("Vouchers to detach: {$voucherCount}"); - $this->line("Children: {$childrenCount}"); - $this->line("Carers (including trashed): {$carersCount}"); + $this->line("Vouchers to detach: $voucherCount"); + $this->line("Children: $childrenCount"); + $this->line("Carers (including trashed): $carersCount"); if ($dryRun) { $this->warn('Dry run complete — nothing deleted.'); @@ -193,7 +160,7 @@ private function purgeSingleFamily(int $familyId, bool $dryRun, bool $force): in if ( !$force && !$this->confirm( - "This will permanently purge family {$familyId} and related data. Continue?" + "This will permanently purge family $familyId and related data. Continue?" ) ) { $this->warn('Skipped'); @@ -212,20 +179,20 @@ private function purgeSingleFamily(int $familyId, bool $dryRun, bool $force): in $deletedChildren = $this->deleteChildren($familyId); if ($deletedChildren !== $childrenCount) { throw new RuntimeException( - "Child delete mismatch. Expected {$childrenCount}, deleted {$deletedChildren}." + "Child delete mismatch. Expected $childrenCount, deleted $deletedChildren." ); } $deletedCarers = $this->deleteCarers($familyId); if ($deletedCarers !== $carersCount) { throw new RuntimeException( - "Carer delete mismatch. Expected {$carersCount}, deleted {$deletedCarers}." + "Carer delete mismatch. Expected $carersCount, deleted $deletedCarers." ); } $deletedFamily = $this->deleteFamily($family); if ($deletedFamily !== 1) { - throw new RuntimeException("Family delete failed for family {$familyId}."); + throw new RuntimeException("Family delete failed for family $familyId."); } $this->info('Family graph permanently deleted.'); @@ -257,7 +224,7 @@ private function deleteBundles(Collection $bundleIds): void if ($deleted !== $expected) { throw new RuntimeException( - "Bundle delete mismatch. Expected {$expected}, deleted {$deleted}." + "Bundle delete mismatch. Expected $expected, deleted $deleted." ); } } @@ -274,7 +241,7 @@ private function deleteRegistrations(Collection $registrationIds): void if ($deleted !== $expected) { throw new RuntimeException( - "Registration delete mismatch. Expected {$expected}, deleted {$deleted}." + "Registration delete mismatch. Expected $expected, deleted $deleted." ); } } diff --git a/app/Family.php b/app/Family.php index 426682e2f..39febe998 100644 --- a/app/Family.php +++ b/app/Family.php @@ -79,6 +79,41 @@ class Family extends Model implements IEvaluee 'rvid', ]; + public static function findByRvid(string $rvid): ?self + { + $rvid = strtoupper(trim($rvid)); + + if ($rvid === '') { + return null; + } + + // IMPORTANT: longest prefix first (prevents AB matching before AB1) + $centres = Centre::query() + ->select('id', 'prefix') + ->orderByRaw('LENGTH(prefix) DESC') + ->get()->all(); + + foreach ($centres as $centre) { + if (!str_starts_with($rvid, $centre->prefix)) { + continue; + } + $sequencePart = substr($rvid, strlen($centre->prefix)); + + if (!ctype_digit($sequencePart)) { + continue; + } + + $sequence = (int)$sequencePart; + + return self::query() + ->where('initial_centre_id', $centre->id) + ->where('centre_sequence', $sequence) + ->first(); + } + + return null; + } + /** * Gets the evaluator from up the chain. * diff --git a/app/Http/Controllers/Service/Admin/SponsorsController.php b/app/Http/Controllers/Service/Admin/SponsorsController.php index d4e44950d..acea4e0f1 100644 --- a/app/Http/Controllers/Service/Admin/SponsorsController.php +++ b/app/Http/Controllers/Service/Admin/SponsorsController.php @@ -126,7 +126,7 @@ public function update(UpdateRulesRequest $request, int $id): RedirectResponse try { Evaluation::updateOrCreate(['sponsor_id' => $id, 'name' => $key], $payload); } catch (Exception $e) { - Log::error("Failed to update evaluation $key for sponsor #{$id} by user " . Auth::id(), [ + Log::error("Failed to update evaluation $key for sponsor #$id by user " . Auth::id(), [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); diff --git a/storage/app/.gitignore b/storage/app/.gitignore deleted file mode 100755 index 8f4803c05..000000000 --- a/storage/app/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!public/ -!.gitignore diff --git a/storage/app/local/.keep b/storage/app/local/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore deleted file mode 100755 index d6b7ef32c..000000000 --- a/storage/app/public/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/storage/framework/testing/.gitignore b/storage/framework/testing/.gitignore deleted file mode 100755 index d6b7ef32c..000000000 --- a/storage/framework/testing/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/tests/Console/Commands/MoveFamilyRegistrationCentreTest.php b/tests/Console/Commands/MoveFamilyRegistrationCentreTest.php new file mode 100644 index 000000000..ff6c69e73 --- /dev/null +++ b/tests/Console/Commands/MoveFamilyRegistrationCentreTest.php @@ -0,0 +1,264 @@ +deleteDirectory('/'); + parent::tearDown(); + } + + public function testItRequiresEitherPairOfArgumentsOrCsv(): void + { + $this->artisan('arc:move-family-reg') + ->expectsOutput('Provide either a family_id and centre_id OR --csv=path') + ->assertExitCode(1); + } + + public function testItRejectsMixingDirectArgumentsWithCsv(): void + { + $csv = $this->makeCsv([ + ['RVID', 'Centre'], + ['ABC123', 'Target Centre'], + ], 'move-family-'); + + $this->artisan('arc:move-family-reg', [ + 'family_id' => 1, + 'centre_id' => 2, + '--csv' => $csv, + ]) + ->expectsOutput('Provide either a family_id and centre_id OR --csv=path') + ->assertExitCode(1); + } + + public function testItFailsWhenCsvFileIsMissing(): void + { + $missing = Storage::path('missing-move.csv'); + + $this->artisan('arc:move-family-reg', [ + '--csv' => $missing, + ]) + ->expectsOutput("CSV file not found: $missing") + ->assertExitCode(1); + } + + public function testItFailsWhenTargetCentreDoesNotExist(): void + { + $family = factory(Family::class)->create(); + + $this->artisan('arc:move-family-reg', [ + 'family_id' => $family->id, + 'centre_id' => 999999, + '--force' => true, + ]) + ->expectsOutput('Processing 1 families') + ->expectsOutput("==== FAMILY $family->id ====") + ->expectsOutput('Centre 999999 not found.') + ->expectsOutput('Batch complete.') + ->expectsOutput('Failed family IDs:') + ->expectsOutput((string) $family->id) + ->assertExitCode(1); + } + + public function testItFailsWhenFamilyDoesNotExist(): void + { + $centre = factory(Centre::class)->create(); + + $this->artisan('arc:move-family-reg', [ + 'family_id' => 999999, + 'centre_id' => $centre->id, + '--force' => true, + ]) + ->expectsOutput('Processing 1 families') + ->expectsOutput('==== FAMILY 999999 ====') + ->expectsOutput('Family 999999 not found.') + ->expectsOutput('Batch complete.') + ->expectsOutput('Failed family IDs:') + ->expectsOutput('999999') + ->assertExitCode(1); + } + + public function testDryRunShowsSummaryAndDoesNotMutateRegistrations(): void + { + $from = factory(Centre::class)->create(['name' => 'From Centre']); + $to = factory(Centre::class)->create(['name' => 'To Centre']); + + $family = factory(Family::class)->create([ + 'initial_centre_id' => $from->id, + ]); + + $registrations = factory(Registration::class, 2)->create([ + 'family_id' => $family->id, + 'centre_id' => $from->id, + ]); + + $this->artisan('arc:move-family-reg', [ + 'family_id' => $family->id, + 'centre_id' => $to->id, + '--dry-run' => true, + ]) + ->expectsOutput('Processing 1 families') + ->expectsOutput("==== FAMILY {$family->id} ====") + ->expectsOutput('Dry run complete — nothing deleted.') + ->expectsOutput('Batch complete.') + ->assertExitCode(0); + + foreach ($registrations as $registration) { + $this->assertDatabaseHas('registrations', [ + 'id' => $registration->id, + 'centre_id' => $from->id, + ]); + } + } + + public function testItSkipsWhenConfirmationIsDeclined(): void + { + $from = factory(Centre::class)->create(['name' => 'From Centre']); + $to = factory(Centre::class)->create(['name' => 'To Centre']); + + $family = factory(Family::class)->create([ + 'initial_centre_id' => $from->id, + ]); + + $registration = factory(Registration::class)->create([ + 'family_id' => $family->id, + 'centre_id' => $from->id, + ]); + + $this->artisan('arc:move-family-reg', [ + 'family_id' => $family->id, + 'centre_id' => $to->id, + ]) + ->expectsConfirmation( + "This will permanently move family {$family->id} and related data to {$to->name}. Continue?" + ) + ->expectsOutput('Skipped') + ->expectsOutput('Batch complete.') + ->expectsOutput('Failed family IDs:') + ->expectsOutput((string) $family->id) + ->assertExitCode(1); + + $this->assertDatabaseHas('registrations', [ + 'id' => $registration->id, + 'centre_id' => $from->id, + ]); + } + + public function testItMovesRegistrationsWhenForced(): void + { + $from = factory(Centre::class)->create(['name' => 'From Centre']); + $to = factory(Centre::class)->create(['name' => 'To Centre']); + + $family = factory(Family::class)->create([ + 'initial_centre_id' => $from->id, + ]); + + $registrations = factory(Registration::class, 3)->create([ + 'family_id' => $family->id, + 'centre_id' => $from->id, + ]); + + $this->artisan('arc:move-family-reg', [ + 'family_id' => $family->id, + 'centre_id' => $to->id, + '--force' => true, + ]) + ->expectsOutput('Processing 1 families') + ->expectsOutput("==== FAMILY {$family->id} ====") + ->expectsOutput('Family Registration permanently moved.') + ->expectsOutput('Batch complete.') + ->assertExitCode(0); + + foreach ($registrations as $registration) { + $this->assertDatabaseHas('registrations', [ + 'id' => $registration->id, + 'centre_id' => $to->id, + ]); + } + } + + public function testItProcessesCsvDedupesPairsAndIgnoresInvalidRows(): void + { + $oldCentre = factory(Centre::class)->create([ + 'name' => 'Old Centre', + 'prefix' => 'AB', + ]); + + $newCentre = factory(Centre::class)->create([ + 'name' => 'New Centre', + 'prefix' => 'XY', + ]); + + $family = factory(Family::class)->create([ + 'initial_centre_id' => $oldCentre->id, + 'centre_sequence' => 123, + ]); + + factory(Registration::class, 2)->create([ + 'family_id' => $family->id, + 'centre_id' => $oldCentre->id, + ]); + + $csvPath = $this->makeCsv([ + ['RVID', 'Centre'], + [$family->rvid, $newCentre->name], + [$family->rvid, $newCentre->name], + ['NOTREAL999', $newCentre->name], + [$family->rvid, 'Missing Centre'], + ], 'move-family-'); + + $this->artisan('arc:move-family-reg', [ + '--csv' => $csvPath, + '--force' => true, + ]) + ->expectsOutput('Invalid RVID: NOTREAL999') + ->expectsOutput('Invalid Centre: Missing Centre') + ->expectsOutput('Processing 1 families') + ->expectsOutput("==== FAMILY {$family->id} ====") + ->expectsOutput('Family Registration permanently moved.') + ->expectsOutput('Batch complete.') + ->assertExitCode(0) + ; + $this->assertDatabaseCount('registrations', 2); + + $regsCount = Registration::where('family_id', $family->id) + ->where('centre_id', $newCentre->id) + ->count(); + + $this->assertSame(2, $regsCount); + } + + private function makeCsv(array $rows, string $name): string + { + $path = uniqid($name, true) . '.csv'; + $stream = fopen('php://temp', 'wb+'); + + foreach ($rows as $row) { + fputcsv($stream, $row); + } + + rewind($stream); + Storage::put($path, stream_get_contents($stream)); + fclose($stream); + + return $path; + } +} diff --git a/tests/Console/Commands/PurgeFamilyGraphTest.php b/tests/Console/Commands/PurgeFamilyGraphTest.php new file mode 100644 index 000000000..dfe309932 --- /dev/null +++ b/tests/Console/Commands/PurgeFamilyGraphTest.php @@ -0,0 +1,251 @@ +deleteDirectory('/'); + parent::tearDown(); + } + + public function testItRequiresFamilyIdOrCsv(): void + { + $this->artisan('arc:purge-family') + ->expectsOutput('Provide either a family_id OR --csv=path') + ->assertExitCode(1); + } + + public function testItFailsWhenCsvFileIsMissing(): void + { + $missing = Storage::path('missing-purge.csv'); + + $this->artisan('arc:purge-family', [ + '--csv' => $missing, + ]) + ->expectsOutput("CSV file not found: {$missing}") + ->assertExitCode(1); + } + + public function testItFailsWhenFamilyIsMissing(): void + { + $this->artisan('arc:purge-family', [ + 'family_id' => 999999, + '--force' => true, + ]) + ->expectsOutput('Processing 1 families') + ->expectsOutput('==== FAMILY 999999 ====') + ->expectsOutput('Family 999999 not found.') + ->expectsOutput('Batch complete.') + ->expectsOutput('Failed family IDs:') + ->expectsOutput('999999') + ->assertExitCode(1); + } + + public function testDryRunReportsButDoesNotDeleteAnything(): void + { + $family = factory(Family::class)->create(); + + $registration = factory(Registration::class)->create([ + 'family_id' => $family->id, + ]); + + $bundle = factory(Bundle::class)->create([ + 'registration_id' => $registration->id, + ]); + + $voucher = factory(Voucher::class)->create([ + 'bundle_id' => $bundle->id, + ]); + + $child = factory(Child::class)->create([ + 'family_id' => $family->id, + ]); + + $carer = factory(Carer::class)->create([ + 'family_id' => $family->id, + ]); + + $this->artisan('arc:purge-family', [ + 'family_id' => $family->id, + '--dry-run' => true, + ]) + ->expectsOutput('Processing 1 families') + ->expectsOutput("==== FAMILY {$family->id} ====") + ->expectsOutput('Dry run complete — nothing deleted.') + ->expectsOutput('Batch complete.') + ->assertExitCode(0); + + $this->assertDatabaseHas('families', ['id' => $family->id]); + $this->assertDatabaseHas('registrations', ['id' => $registration->id]); + $this->assertDatabaseHas('bundles', ['id' => $bundle->id]); + $this->assertDatabaseHas('vouchers', ['id' => $voucher->id, 'bundle_id' => $bundle->id]); + $this->assertDatabaseHas('children', ['id' => $child->id]); + $this->assertDatabaseHas('carers', ['id' => $carer->id]); + } + + public function testItSkipsWhenConfirmationIsDeclined(): void + { + $family = factory(Family::class)->create(); + + $this->artisan('arc:purge-family', [ + 'family_id' => $family->id, + ]) + ->expectsConfirmation( + "This will permanently purge family {$family->id} and related data. Continue?" + ) + ->expectsOutput('Skipped') + ->expectsOutput('Batch complete.') + ->expectsOutput('Failed family IDs:') + ->expectsOutput((string) $family->id) + ->assertExitCode(1); + + $this->assertDatabaseHas('families', ['id' => $family->id]); + } + + public function testItPurgesTheEntireFamilyGraphWhenForced(): void + { + $family = factory(Family::class)->create(); + + $registrationA = factory(Registration::class)->create([ + 'family_id' => $family->id, + ]); + + $registrationB = factory(Registration::class)->create([ + 'family_id' => $family->id, + ]); + + $bundleA = factory(Bundle::class)->create([ + 'registration_id' => $registrationA->id, + ]); + + $bundleB = factory(Bundle::class)->create([ + 'registration_id' => $registrationB->id, + ]); + + $voucherA = factory(Voucher::class)->create([ + 'bundle_id' => $bundleA->id, + ]); + + $voucherB = factory(Voucher::class)->create([ + 'bundle_id' => $bundleB->id, + ]); + + $children = factory(Child::class, 2)->create([ + 'family_id' => $family->id, + ]); + + $activeCarer = factory(Carer::class)->create([ + 'family_id' => $family->id, + ]); + + $trashedCarer = factory(Carer::class)->create([ + 'family_id' => $family->id, + ]); + + $trashedCarer->delete(); + + $this->artisan('arc:purge-family', [ + 'family_id' => $family->id, + '--force' => true, + ]) + ->expectsOutput('Processing 1 families') + ->expectsOutput("==== FAMILY {$family->id} ====") + ->expectsOutput('Family graph permanently deleted.') + ->expectsOutput('Batch complete.') + ->assertExitCode(0); + + $this->assertDatabaseMissing('families', ['id' => $family->id]); + $this->assertDatabaseMissing('registrations', ['id' => $registrationA->id]); + $this->assertDatabaseMissing('registrations', ['id' => $registrationB->id]); + $this->assertDatabaseMissing('bundles', ['id' => $bundleA->id]); + $this->assertDatabaseMissing('bundles', ['id' => $bundleB->id]); + + foreach ($children as $child) { + $this->assertDatabaseMissing('children', ['id' => $child->id]); + } + + $this->assertDatabaseMissing('carers', ['id' => $activeCarer->id]); + $this->assertDatabaseMissing('carers', ['id' => $trashedCarer->id]); + + $this->assertDatabaseHas('vouchers', [ + 'id' => $voucherA->id, + 'bundle_id' => null, + ]); + + $this->assertDatabaseHas('vouchers', [ + 'id' => $voucherB->id, + 'bundle_id' => null, + ]); + } + + public function testItProcessesCsvDedupesIdsAndIgnoresInvalidRvids(): void + { + $centre = factory(Centre::class)->create([ + 'prefix' => 'RV', + ]); + + $family = factory(Family::class)->create([ + 'initial_centre_id' => $centre->id, + 'centre_sequence' => 42, + ]); + + $csv = $this->makeCsv([ + ['RVID'], + [$family->rvid], + [$family->rvid], + ['BAD999'], + ], 'purge-family-'); + + $this->artisan('arc:purge-family', [ + '--csv' => $csv, + '--force' => true, + ]) + ->expectsOutput('Invalid rvid: BAD999') + ->expectsOutput('Processing 1 families') + ->expectsOutput("==== FAMILY {$family->id} ====") + ->expectsOutput('Family graph permanently deleted.') + ->expectsOutput('Batch complete.') + ->assertExitCode(0); + + $this->assertDatabaseMissing('families', ['id' => $family->id]); + } + + + private function makeCsv(array $rows, string $name): string + { + $path = uniqid($name, true) . '.csv'; + $stream = fopen('php://temp', 'wb+'); + + foreach ($rows as $row) { + fputcsv($stream, $row); + } + + rewind($stream); + Storage::put($path, stream_get_contents($stream)); + fclose($stream); + + return $path; + } +} diff --git a/tests/Feature/Service/LoggingTest.php b/tests/Feature/Service/LoggingTest.php index ac1e734fa..dc5b4b01e 100644 --- a/tests/Feature/Service/LoggingTest.php +++ b/tests/Feature/Service/LoggingTest.php @@ -16,9 +16,10 @@ class LoggingTest extends StoreTestCase /** @var array $postData */ private array $postData; - public function setUp(): void + protected function setUp(): void { parent::setUp(); + Storage::fake('log'); $this->faker = Factory::create(); $this->faker->seed(1234); @@ -30,17 +31,18 @@ public function setUp(): void ]; } + protected function tearDown(): void + { + // tidy up last test + Storage::disk('log')->deleteDirectory('/'); + parent::tearDown(); + } + /** * @return void */ public function testItLogsData(): void { - $storage = Storage::fake('log'); - - Storage::shouldReceive('disk') - ->with('log') - ->andReturn($storage); - $request = new MockRequest($this->postData); $loggingController = new LoggingController(); $response = $loggingController->log($request); diff --git a/tests/Unit/Models/FamilyModelTest.php b/tests/Unit/Models/FamilyModelTest.php index aa139a279..719c32e4d 100644 --- a/tests/Unit/Models/FamilyModelTest.php +++ b/tests/Unit/Models/FamilyModelTest.php @@ -14,7 +14,6 @@ class FamilyModelTest extends TestCase { use RefreshDatabase; - public function testItCanHaveRegistrations(): void { // Create Family @@ -257,4 +256,38 @@ public function testItCanGetsARvidCorrectlyForGivenCentre(): void $this->assertEquals($candidate, $family->rvid); } + + public function testFindByRvidReturnsNullForBlankOrInvalidValues(): void + { + $this->assertNull(Family::findByRvid('')); + $this->assertNull(Family::findByRvid(' ')); + $this->assertNull(Family::findByRvid('NOT-AN-RVID')); + } + + public function testItResolvesLongestPrefixFirstWhenFindingFamilyByRvid(): void + { + $short = factory(Centre::class)->create([ + 'prefix' => 'AB', + ]); + + $long = factory(Centre::class)->create([ + 'prefix' => 'AB1', + ]); + + $wrongFamily = factory(Family::class)->create([ + 'initial_centre_id' => $short->id, + 'centre_sequence' => 23, + ]); + + $expectedFamily = factory(Family::class)->create([ + 'initial_centre_id' => $long->id, + 'centre_sequence' => 23, + ]); + + $resolved = Family::findByRvid('ab123'); + + $this->assertNotNull($resolved); + $this->assertSame($expectedFamily->id, $resolved->id); + $this->assertNotSame($wrongFamily->id, $resolved->id); + } } From 60d169a61e1925c3d4603c65b4e947f6869600e9 Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 24 Mar 2026 16:27:26 +0000 Subject: [PATCH 016/168] feat: update privacy policy link in config file --- config/arc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/arc.php b/config/arc.php index 74f3f60d4..adf54165f 100644 --- a/config/arc.php +++ b/config/arc.php @@ -50,7 +50,7 @@ |-------------------------------------------------------------------------- */ 'links' => [ - 'privacy_policy' => 'https://www.alexandrarose.org.uk/arc-childrens-centre-information-sharing-policy', + 'privacy_policy' => 'https://www.alexandrarose.org.uk/wp-content/uploads/2025/02/Privacy-Policy-Rose-Voucher-Recipient-Feb-25.pdf', ], /* From c2ce94ac074f0db7086b1357c16eb69d8b235d17 Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 25 Mar 2026 15:23:17 +0000 Subject: [PATCH 017/168] chore: upgrade packages to support l12 --- composer.json | 12 +- composer.lock | 534 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 331 insertions(+), 215 deletions(-) diff --git a/composer.json b/composer.json index 2fa040ee5..c14a41d31 100644 --- a/composer.json +++ b/composer.json @@ -21,12 +21,12 @@ "ext-pdo": "*", "ext-sodium": "*", "ext-zip": "*", - "barryvdh/laravel-debugbar": "v3.16.5", + "barryvdh/laravel-debugbar": "v4.1.3", "barryvdh/laravel-dompdf": "v3.1.2", "fakerphp/faker": "1.24.1", "highsolutions/laravel-searchy": "12.0", "imtigger/laravel-job-status": "1.2.0", - "laravel/framework": "v11.50.0", + "laravel/framework": "v12.55.1", "laravel/helpers": "v1.8.3", "laravel/legacy-factories": "v1.4.2", "laravel/passport": "v12.4.3", @@ -41,19 +41,19 @@ "symfony/mailchimp-mailer": "v7.4.6", "usmanhalalit/laracsv": "2.1.0", "werk365/etagconditionals": "dev-master", - "ext-simplexml": "*" + "ext-simplexml": "*" }, "require-dev": { "barryvdh/laravel-ide-helper": "v3.7.0", "friendsofphp/php-cs-fixer": "v3.94.2", "larastan/larastan": "v3.9.3", "laravel/browser-kit-testing": "v7.2.8", - "laravel/dusk": "v8.4.1", + "laravel/dusk": "v8.5.0", "mockery/mockery": "1.6.12", "nunomaduro/collision": "v8.9.1", "phpmd/phpmd": "2.15.0", - "phpstan/phpstan": "2.1.42", - "phpunit/phpunit": "11.5.50", + "phpstan/phpstan": "2.1.43", + "phpunit/phpunit": "11.5.55", "spatie/laravel-ignition": "2.12.0", "squizlabs/php_codesniffer": "4.0.1" }, diff --git a/composer.lock b/composer.lock index daaf744a1..58fe1cf90 100644 --- a/composer.lock +++ b/composer.lock @@ -4,48 +4,57 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d6fa7bf2c9ba42a71467abc6ce953861", + "content-hash": "0954b6c405aeaacb84391c2d1ed603b6", "packages": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.16.5", + "version": "v4.1.3", "source": { "type": "git", "url": "https://github.com/fruitcake/laravel-debugbar.git", - "reference": "e85c0a8464da67e5b4a53a42796d46a43fc06c9a" + "reference": "b48a68c4f8ffcdfa3a10d49930da4b03588dc87b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/laravel-debugbar/zipball/e85c0a8464da67e5b4a53a42796d46a43fc06c9a", - "reference": "e85c0a8464da67e5b4a53a42796d46a43fc06c9a", + "url": "https://api.github.com/repos/fruitcake/laravel-debugbar/zipball/b48a68c4f8ffcdfa3a10d49930da4b03588dc87b", + "reference": "b48a68c4f8ffcdfa3a10d49930da4b03588dc87b", "shasum": "" }, "require": { - "illuminate/routing": "^10|^11|^12", - "illuminate/session": "^10|^11|^12", - "illuminate/support": "^10|^11|^12", - "php": "^8.1", - "php-debugbar/php-debugbar": "^2.2.4", - "symfony/finder": "^6|^7|^8" + "illuminate/routing": "^11|^12|^13.0", + "illuminate/session": "^11|^12|^13.0", + "illuminate/support": "^11|^12|^13.0", + "php": "^8.2", + "php-debugbar/php-debugbar": "^3.5", + "php-debugbar/symfony-bridge": "^1.1" }, "require-dev": { + "larastan/larastan": "^3", + "laravel/octane": "^2", + "laravel/pennant": "^1", + "laravel/pint": "^1", + "laravel/telescope": "^5.16", + "livewire/livewire": "^3.7|^4", "mockery/mockery": "^1.3.3", - "orchestra/testbench-dusk": "^7|^8|^9|^10", - "phpunit/phpunit": "^9.5.10|^10|^11", - "squizlabs/php_codesniffer": "^3.5" + "orchestra/testbench-dusk": "^9|^10|^11", + "php-debugbar/twig-bridge": "^2.0", + "phpstan/phpstan-phpunit": "^2", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^11", + "shipmonk/phpstan-rules": "^4.3" }, "type": "library", "extra": { "laravel": { "aliases": { - "Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar" + "Debugbar": "Fruitcake\\LaravelDebugbar\\Facades\\Debugbar" }, "providers": [ - "Barryvdh\\Debugbar\\ServiceProvider" + "Fruitcake\\LaravelDebugbar\\ServiceProvider" ] }, "branch-alias": { - "dev-master": "3.16-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -53,7 +62,7 @@ "src/helpers.php" ], "psr-4": { - "Barryvdh\\Debugbar\\": "src/" + "Fruitcake\\LaravelDebugbar\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -61,6 +70,10 @@ "MIT" ], "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, { "name": "Barry vd. Heuvel", "email": "barryvdh@gmail.com" @@ -68,6 +81,7 @@ ], "description": "PHP Debugbar integration for Laravel", "keywords": [ + "barryvdh", "debug", "debugbar", "dev", @@ -77,7 +91,7 @@ ], "support": { "issues": "https://github.com/fruitcake/laravel-debugbar/issues", - "source": "https://github.com/fruitcake/laravel-debugbar/tree/v3.16.5" + "source": "https://github.com/fruitcake/laravel-debugbar/tree/v4.1.3" }, "funding": [ { @@ -89,7 +103,7 @@ "type": "github" } ], - "time": "2026-01-23T15:03:22+00:00" + "time": "2026-03-09T14:55:04+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -1696,20 +1710,20 @@ }, { "name": "laravel/framework", - "version": "v11.50.0", + "version": "v12.55.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "c761f591209b45f56c1317ecbff0b04c89cf7ba2" + "reference": "6d9185a248d101b07eecaf8fd60b18129545fd33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/c761f591209b45f56c1317ecbff0b04c89cf7ba2", - "reference": "c761f591209b45f56c1317ecbff0b04c89cf7ba2", + "url": "https://api.github.com/repos/laravel/framework/zipball/6d9185a248d101b07eecaf8fd60b18129545fd33", + "reference": "6d9185a248d101b07eecaf8fd60b18129545fd33", "shasum": "" }, "require": { - "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12|^0.13|^0.14", + "brick/math": "^0.11|^0.12|^0.13|^0.14", "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.4", @@ -1724,32 +1738,34 @@ "fruitcake/php-cors": "^1.3", "guzzlehttp/guzzle": "^7.8.2", "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", + "laravel/prompts": "^0.3.0", "laravel/serializable-closure": "^1.3|^2.0", - "league/commonmark": "^2.7", + "league/commonmark": "^2.8.1", "league/flysystem": "^3.25.1", "league/flysystem-local": "^3.25.1", "league/uri": "^7.5.1", "monolog/monolog": "^3.0", - "nesbot/carbon": "^2.72.6|^3.8.4", + "nesbot/carbon": "^3.8.4", "nunomaduro/termwind": "^2.0", "php": "^8.2", "psr/container": "^1.1.1|^2.0.1", "psr/log": "^1.0|^2.0|^3.0", "psr/simple-cache": "^1.0|^2.0|^3.0", "ramsey/uuid": "^4.7", - "symfony/console": "^7.0.3", - "symfony/error-handler": "^7.0.3", - "symfony/finder": "^7.0.3", + "symfony/console": "^7.2.0", + "symfony/error-handler": "^7.2.0", + "symfony/finder": "^7.2.0", "symfony/http-foundation": "^7.2.0", - "symfony/http-kernel": "^7.0.3", - "symfony/mailer": "^7.0.3", - "symfony/mime": "^7.0.3", - "symfony/polyfill-php83": "^1.31", - "symfony/process": "^7.0.3", - "symfony/routing": "^7.0.3", - "symfony/uid": "^7.0.3", - "symfony/var-dumper": "^7.0.3", + "symfony/http-kernel": "^7.2.0", + "symfony/mailer": "^7.2.0", + "symfony/mime": "^7.2.0", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33", + "symfony/process": "^7.2.0", + "symfony/routing": "^7.2.0", + "symfony/uid": "^7.2.0", + "symfony/var-dumper": "^7.2.0", "tijsverkoyen/css-to-inline-styles": "^2.2.5", "vlucas/phpdotenv": "^5.6.1", "voku/portable-ascii": "^2.0.2" @@ -1781,6 +1797,7 @@ "illuminate/filesystem": "self.version", "illuminate/hashing": "self.version", "illuminate/http": "self.version", + "illuminate/json-schema": "self.version", "illuminate/log": "self.version", "illuminate/macroable": "self.version", "illuminate/mail": "self.version", @@ -1790,6 +1807,7 @@ "illuminate/process": "self.version", "illuminate/queue": "self.version", "illuminate/redis": "self.version", + "illuminate/reflection": "self.version", "illuminate/routing": "self.version", "illuminate/session": "self.version", "illuminate/support": "self.version", @@ -1813,17 +1831,18 @@ "league/flysystem-read-only": "^3.25.1", "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", - "orchestra/testbench-core": "^9.18.0", - "pda/pheanstalk": "^5.0.6", + "opis/json-schema": "^2.4.1", + "orchestra/testbench-core": "^10.9.0", + "pda/pheanstalk": "^5.0.6|^7.0.0", "php-http/discovery": "^1.15", - "phpstan/phpstan": "2.1.41", - "phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1", - "predis/predis": "^2.3", - "resend/resend-php": "^0.10.0", - "symfony/cache": "^7.0.3", - "symfony/http-client": "^7.0.3", - "symfony/psr-http-message-bridge": "^7.0.3", - "symfony/translation": "^7.0.3" + "phpstan/phpstan": "^2.1.41", + "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", + "predis/predis": "^2.3|^3.0", + "resend/resend-php": "^0.10.0|^1.0", + "symfony/cache": "^7.2.0", + "symfony/http-client": "^7.2.0", + "symfony/psr-http-message-bridge": "^7.2.0", + "symfony/translation": "^7.2.0" }, "suggest": { "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", @@ -1838,7 +1857,7 @@ "ext-pdo": "Required to use all database features.", "ext-posix": "Required to use all features of the queue worker.", "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0|^6.0).", - "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.23).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", @@ -1849,22 +1868,22 @@ "mockery/mockery": "Required to use mocking (^1.6).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", - "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.3.6|^12.0.1).", - "predis/predis": "Required to use the predis connector (^2.3).", + "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1).", + "predis/predis": "Required to use the predis connector (^2.3|^3.0).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", - "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^7.0).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).", - "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.0).", - "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.0).", - "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.0).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.0)." + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0|^1.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^7.2).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.2).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.2).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.2).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.2)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -1875,6 +1894,7 @@ "src/Illuminate/Filesystem/functions.php", "src/Illuminate/Foundation/helpers.php", "src/Illuminate/Log/functions.php", + "src/Illuminate/Reflection/helpers.php", "src/Illuminate/Support/functions.php", "src/Illuminate/Support/helpers.php" ], @@ -1883,7 +1903,8 @@ "Illuminate\\Support\\": [ "src/Illuminate/Macroable/", "src/Illuminate/Collections/", - "src/Illuminate/Conditionable/" + "src/Illuminate/Conditionable/", + "src/Illuminate/Reflection/" ] } }, @@ -1907,7 +1928,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-03-17T19:35:22+00:00" + "time": "2026-03-18T14:28:59+00:00" }, { "name": "laravel/helpers", @@ -2100,16 +2121,16 @@ }, { "name": "laravel/prompts", - "version": "v0.3.15", + "version": "v0.3.16", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "4bb8107ec97651fd3f17f897d6489dbc4d8fb999" + "reference": "11e7d5f93803a2190b00e145142cb00a33d17ad2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/4bb8107ec97651fd3f17f897d6489dbc4d8fb999", - "reference": "4bb8107ec97651fd3f17f897d6489dbc4d8fb999", + "url": "https://api.github.com/repos/laravel/prompts/zipball/11e7d5f93803a2190b00e145142cb00a33d17ad2", + "reference": "11e7d5f93803a2190b00e145142cb00a33d17ad2", "shasum": "" }, "require": { @@ -2153,9 +2174,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.15" + "source": "https://github.com/laravel/prompts/tree/v0.3.16" }, - "time": "2026-03-17T13:45:17+00:00" + "time": "2026-03-23T14:35:33+00:00" }, { "name": "laravel/serializable-closure", @@ -2820,16 +2841,16 @@ }, { "name": "league/flysystem", - "version": "3.32.0", + "version": "3.33.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725" + "reference": "570b8871e0ce693764434b29154c54b434905350" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/254b1595b16b22dbddaaef9ed6ca9fdac4956725", - "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/570b8871e0ce693764434b29154c54b434905350", + "reference": "570b8871e0ce693764434b29154c54b434905350", "shasum": "" }, "require": { @@ -2897,9 +2918,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.32.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.33.0" }, - "time": "2026-02-25T17:01:41+00:00" + "time": "2026-03-25T07:59:30+00:00" }, { "name": "league/flysystem-local", @@ -4187,47 +4208,63 @@ }, { "name": "php-debugbar/php-debugbar", - "version": "v2.2.6", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/php-debugbar/php-debugbar.git", - "reference": "abb9fa3c5c8dbe7efe03ddba56782917481de3e8" + "reference": "486b32fd98efe9a3c10f0b24c0caabc187f78f04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/abb9fa3c5c8dbe7efe03ddba56782917481de3e8", - "reference": "abb9fa3c5c8dbe7efe03ddba56782917481de3e8", + "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/486b32fd98efe9a3c10f0b24c0caabc187f78f04", + "reference": "486b32fd98efe9a3c10f0b24c0caabc187f78f04", "shasum": "" }, "require": { - "php": "^8.1", + "php": "^8.2", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^5.4|^6.4|^7.3|^8.0" + "symfony/var-dumper": "^5.4|^6|^7|^8" }, "replace": { "maximebf/debugbar": "self.version" }, "require-dev": { - "dbrekelmans/bdi": "^1", + "dbrekelmans/bdi": "^1.4", + "friendsofphp/php-cs-fixer": "^3.92", + "monolog/monolog": "^3.9", + "php-debugbar/doctrine-bridge": "^3@dev", + "php-debugbar/monolog-bridge": "^1@dev", + "php-debugbar/symfony-bridge": "^1@dev", + "php-debugbar/twig-bridge": "^2@dev", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^10", - "symfony/browser-kit": "^6.0|7.0", + "predis/predis": "^3.3", + "shipmonk/phpstan-rules": "^4.3", + "symfony/browser-kit": "^6.4|7.0", + "symfony/dom-crawler": "^6.4|^7", + "symfony/event-dispatcher": "^5.4|^6.4|^7.3|^8.0", + "symfony/http-foundation": "^5.4|^6.4|^7.3|^8.0", + "symfony/mailer": "^5.4|^6.4|^7.3|^8.0", "symfony/panther": "^1|^2.1", "twig/twig": "^3.11.2" }, "suggest": { - "kriswallsmith/assetic": "The best way to manage assets", - "monolog/monolog": "Log using Monolog", - "predis/predis": "Redis storage" + "php-debugbar/doctrine-bridge": "To integrate Doctrine with php-debugbar.", + "php-debugbar/monolog-bridge": "To integrate Monolog with php-debugbar.", + "php-debugbar/symfony-bridge": "To integrate Symfony with php-debugbar.", + "php-debugbar/twig-bridge": "To integrate Twig with php-debugbar." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "3.0-dev" } }, "autoload": { "psr-4": { - "DebugBar\\": "src/DebugBar/" + "DebugBar\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4251,13 +4288,91 @@ "debug", "debug bar", "debugbar", - "dev" + "dev", + "profiler", + "toolbar" ], "support": { "issues": "https://github.com/php-debugbar/php-debugbar/issues", - "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.2.6" + "source": "https://github.com/php-debugbar/php-debugbar/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2026-03-05T20:37:33+00:00" + }, + { + "name": "php-debugbar/symfony-bridge", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-debugbar/symfony-bridge.git", + "reference": "e37d2debe5d316408b00d0ab2688d9c2cf59b5ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-debugbar/symfony-bridge/zipball/e37d2debe5d316408b00d0ab2688d9c2cf59b5ad", + "reference": "e37d2debe5d316408b00d0ab2688d9c2cf59b5ad", + "shasum": "" + }, + "require": { + "php": "^8.2", + "php-debugbar/php-debugbar": "^3.1", + "symfony/http-foundation": "^5.4|^6.4|^7.3|^8.0" + }, + "require-dev": { + "dbrekelmans/bdi": "^1.4", + "phpunit/phpunit": "^10", + "symfony/browser-kit": "^6|^7", + "symfony/dom-crawler": "^6|^7", + "symfony/mailer": "^5.4|^6.4|^7.3|^8.0", + "symfony/panther": "^1|^2.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "DebugBar\\Bridge\\Symfony\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maxime Bouroumeau-Fuseau", + "email": "maxime.bouroumeau@gmail.com", + "homepage": "http://maximebf.com" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Symfony bridge for PHP Debugbar", + "homepage": "https://github.com/php-debugbar/php-debugbar", + "keywords": [ + "debugbar", + "dev", + "symfony" + ], + "support": { + "issues": "https://github.com/php-debugbar/symfony-bridge/issues", + "source": "https://github.com/php-debugbar/symfony-bridge/tree/v1.1.0" }, - "time": "2025-12-22T13:21:32+00:00" + "time": "2026-01-15T14:47:34+00:00" }, { "name": "phpoption/phpoption", @@ -4907,16 +5022,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.21", + "version": "v0.12.22", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97" + "reference": "3be75d5b9244936dd4ac62ade2bfb004d13acf0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", - "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/3be75d5b9244936dd4ac62ade2bfb004d13acf0f", + "reference": "3be75d5b9244936dd4ac62ade2bfb004d13acf0f", "shasum": "" }, "require": { @@ -4980,9 +5095,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.21" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.22" }, - "time": "2026-03-06T21:21:28+00:00" + "time": "2026-03-22T23:03:24+00:00" }, { "name": "ralouphie/getallheaders", @@ -7493,6 +7608,86 @@ ], "time": "2025-07-08T02:45:35+00:00" }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, { "name": "symfony/polyfill-php85", "version": "v1.33.0", @@ -9176,14 +9371,14 @@ "source": { "type": "git", "url": "https://github.com/neontribe/etagconditionals.git", - "reference": "b0e07ea9d1abd9feef53160b9bb80aff06e96a60" + "reference": "62d6c9f2c113853e44ff6835591c9201d0da6052" }, "require": { - "illuminate/support": "~7|~8|~9|~10|~11" + "illuminate/support": "~7|~8|~9|~10|~11|~12" }, "require-dev": { - "orchestra/testbench": "~5|~6|~7|~8|^9.0", - "phpunit/phpunit": "~8.0|~9.0|~10.5" + "orchestra/testbench": "~5|~6|~7|~8", + "phpunit/phpunit": "~8.0|~9.0" }, "default-branch": true, "type": "library", @@ -9223,30 +9418,30 @@ "EtagConditionals", "Laravel" ], - "time": "2025-01-08T10:10:15+00:00" + "time": "2025-08-26T12:51:50+00:00" }, { "name": "winzou/state-machine", - "version": "0.4.4", + "version": "0.4.6", "source": { "type": "git", "url": "https://github.com/winzou/state-machine.git", - "reference": "b7ced0a415a07a2368e4f67dffaa600a5773fb86" + "reference": "1a3db37daf0a614a57008c780ff9063529bc689b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/winzou/state-machine/zipball/b7ced0a415a07a2368e4f67dffaa600a5773fb86", - "reference": "b7ced0a415a07a2368e4f67dffaa600a5773fb86", + "url": "https://api.github.com/repos/winzou/state-machine/zipball/1a3db37daf0a614a57008c780ff9063529bc689b", + "reference": "1a3db37daf0a614a57008c780ff9063529bc689b", "shasum": "" }, "require": { - "php": "^7.4|^8.0|^8.1", - "symfony/event-dispatcher": "^4.4|^5.4|^6.0|^7.0", - "symfony/expression-language": "^4.4|^5.4|^6.0|^7.0", - "symfony/property-access": "^4.4|^5.4|^6.0|^7.0" + "php": "^7.4|^8.0", + "symfony/event-dispatcher": "^4.4|^5.4|^6.0|^7.0|^8.0", + "symfony/expression-language": "^4.4|^5.4|^6.0|^7.0|^8.0", + "symfony/property-access": "^4.4|^5.4|^6.0|^7.0|^8.0" }, "require-dev": { - "phpspec/phpspec": "^5.0|^6.0|^7.1", + "phpspec/phpspec": "^5.0|^6.0|^7.1|^8.0", "twig/twig": "^2.10|^3.0" }, "suggest": { @@ -9279,9 +9474,9 @@ ], "support": { "issues": "https://github.com/winzou/state-machine/issues", - "source": "https://github.com/winzou/state-machine/tree/0.4.4" + "source": "https://github.com/winzou/state-machine/tree/0.4.6" }, - "time": "2024-01-29T09:14:41+00:00" + "time": "2026-03-24T10:04:32+00:00" } ], "packages-dev": [ @@ -10316,16 +10511,16 @@ }, { "name": "laravel/dusk", - "version": "v8.4.1", + "version": "v8.5.0", "source": { "type": "git", "url": "https://github.com/laravel/dusk.git", - "reference": "3e6548a0e183b408d7af4cf50cd6a50b66061add" + "reference": "f9f75666bed46d1ebca13792447be6e753f4e790" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/dusk/zipball/3e6548a0e183b408d7af4cf50cd6a50b66061add", - "reference": "3e6548a0e183b408d7af4cf50cd6a50b66061add", + "url": "https://api.github.com/repos/laravel/dusk/zipball/f9f75666bed46d1ebca13792447be6e753f4e790", + "reference": "f9f75666bed46d1ebca13792447be6e753f4e790", "shasum": "" }, "require": { @@ -10384,9 +10579,9 @@ ], "support": { "issues": "https://github.com/laravel/dusk/issues", - "source": "https://github.com/laravel/dusk/tree/v8.4.1" + "source": "https://github.com/laravel/dusk/tree/v8.5.0" }, - "time": "2026-03-10T19:59:33+00:00" + "time": "2026-03-21T11:50:49+00:00" }, { "name": "mockery/mockery", @@ -10959,11 +11154,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.42", + "version": "2.1.43", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1279e1ce86ba768f0780c9d889852b4e02ff40d0", - "reference": "1279e1ce86ba768f0780c9d889852b4e02ff40d0", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d01bebe3edfd4d49b9666ee5b8271ddca561042f", + "reference": "d01bebe3edfd4d49b9666ee5b8271ddca561042f", "shasum": "" }, "require": { @@ -11008,7 +11203,7 @@ "type": "github" } ], - "time": "2026-03-17T14:58:32+00:00" + "time": "2026-03-24T20:40:50+00:00" }, { "name": "phpunit/php-code-coverage", @@ -11359,16 +11554,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.50", + "version": "11.5.55", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3" + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", - "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", "shasum": "" }, "require": { @@ -11383,7 +11578,7 @@ "phar-io/version": "^3.2.1", "php": ">=8.2", "phpunit/php-code-coverage": "^11.0.12", - "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-file-iterator": "^5.1.1", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", @@ -11395,6 +11590,7 @@ "sebastian/exporter": "^6.3.2", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", + "sebastian/recursion-context": "^6.0.3", "sebastian/type": "^5.1.3", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" @@ -11440,7 +11636,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.50" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" }, "funding": [ { @@ -11464,7 +11660,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T05:59:18+00:00" + "time": "2026-02-18T12:37:06+00:00" }, { "name": "react/cache", @@ -13950,86 +14146,6 @@ ], "time": "2024-09-09T11:45:10+00:00" }, - { - "name": "symfony/polyfill-php84", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php84\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-06-24T13:30:11+00:00" - }, { "name": "symfony/stopwatch", "version": "v7.4.0", From c47f6db1a25e1fa02729a787c949f40d7dfae53a Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 25 Mar 2026 17:14:52 +0000 Subject: [PATCH 018/168] chore: reconcile L12 config files --- .editorconfig | 18 ++++++++++ .env.example | 2 ++ .gitignore | 81 ++++++++++++++++++++++++++++-------------- config/app.php | 2 +- config/auth.php | 2 +- config/cache.php | 13 +++++-- config/database.php | 23 +++++++++--- config/filesystems.php | 3 +- config/logging.php | 9 +++-- config/mail.php | 5 +-- config/queue.php | 22 +++++++++--- config/services.php | 10 +++--- config/session.php | 10 +++--- phpunit.xml | 8 +++-- 14 files changed, 150 insertions(+), 58 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..a186cd207 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[compose.yaml] +indent_size = 4 diff --git a/.env.example b/.env.example index 631200130..2f5509a53 100644 --- a/.env.example +++ b/.env.example @@ -41,6 +41,8 @@ QUEUE_CONNECTION=database # Set this to true for production envs SESSION_SECURE_COOKIE=false +# PHP_CLI_SERVER_WORKERS=4 + MAIL_MAILER=log MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 diff --git a/.gitignore b/.gitignore index 73b853378..874d673bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,41 +1,68 @@ +# Cache files +*.swp +.DS_STORE +.php-cs-fixer.cache +.php_cs.cache .phpstorm.meta.php .phpunit.result.cache -.php_cs.cache +/.phpunit.cache +/_ide_helper_models.php +Thumbs.db _ide_helper.php + +# Logs +*.log + +# IDEs +/.fleet +/.idea +/.nova +/.vscode +/.zed + +# Dependencies /node_modules +/vendor + +# Build +/public/build +/public/css +/public/fonts /public/hot +/public/images/ +/public/js +/public/store/css/datepicker.css +/resources/fonts/* +build +npm-debug.log + +# Testing & Dusk +arc_test_file_* +coverage +tests/Browser/console/ +tests/Browser/screenshots/ + +# Storage +app/local /public/storage /storage -/vendor -/.idea + +# Homestead /.vagrant Homestead.json Homestead.yaml -npm-debug.log + +# Configs .env -.env* -.prettierignore -.vscode -.DS_STORE +.env.backup .env.dusk.local -tests/Browser/console/ -tests/Browser/screenshots/ +.env.local +.env.production +.phpactor.json +.prettierignore +/auth.json +passport.install -# webpack results -/public/css -/public/fonts -/public/js -/resources/fonts/* -/_ide_helper_models.php -/yarn-error.log -/public/store/css/datepicker.css -/public/images/ +# Docker .docker-installed -passport.install -coverage -.php-cs-fixer.cache -*.swp -app/local -.env.local -arc_test_file_* -build + diff --git a/config/app.php b/config/app.php index 580e03004..73eb1cc8d 100644 --- a/config/app.php +++ b/config/app.php @@ -127,7 +127,7 @@ 'previous_keys' => [ ...array_filter( - explode(',', env('APP_PREVIOUS_KEYS', '')) + explode(',', (string) env('APP_PREVIOUS_KEYS', '')) ), ], diff --git a/config/auth.php b/config/auth.php index 38d284616..45bad68cb 100644 --- a/config/auth.php +++ b/config/auth.php @@ -150,7 +150,7 @@ | Password Confirmation Timeout |-------------------------------------------------------------------------- | - | Here you may define the amount of seconds before a password confirmation + | Here you may define the number of seconds before a password confirmation | window expires and users are asked to re-enter their password via the | confirmation screen. By default, the timeout lasts for three hours. | diff --git a/config/cache.php b/config/cache.php index eb631eeed..af3b0f1d3 100644 --- a/config/cache.php +++ b/config/cache.php @@ -28,7 +28,8 @@ | same cache driver to group types of items stored in your caches. | | Supported drivers: "array", "database", "file", "memcached", - | "redis", "dynamodb", "octane", "null" + | "redis", "dynamodb", "octane", + | "failover", "null" | */ @@ -91,6 +92,14 @@ 'driver' => 'octane', ], + 'failover' => [ + 'driver' => 'failover', + 'stores' => [ + 'database', + 'array', + ], + ], + ], /* @@ -104,6 +113,6 @@ | */ - 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), + 'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'), ]; diff --git a/config/database.php b/config/database.php index ce4a56ffb..c261a130e 100644 --- a/config/database.php +++ b/config/database.php @@ -1,6 +1,7 @@ null, 'journal_mode' => null, 'synchronous' => null, + # be explicit about avoiding aggressive locks + 'transaction_mode' => 'DEFERRED', ], 'testing' => [ @@ -51,6 +54,8 @@ 'busy_timeout' => null, 'journal_mode' => null, 'synchronous' => null, + # be explicit about avoiding aggressive locks + 'transaction_mode' => 'DEFERRED', ], 'mysql' => [ @@ -69,7 +74,7 @@ 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ - PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + (PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], @@ -90,7 +95,7 @@ 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ - PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + (PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], @@ -110,7 +115,7 @@ 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ - PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + (PHP_VERSION_ID >= 80500 ? Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], @@ -126,7 +131,7 @@ 'prefix' => '', 'prefix_indexes' => true, 'search_path' => 'public', - 'sslmode' => 'prefer', + 'sslmode' => env('DB_SSLMODE', 'prefer'), ], 'sqlsrv' => [ @@ -179,7 +184,7 @@ 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), - 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + 'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'), ], 'default' => [ @@ -189,6 +194,10 @@ 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_DB', '0'), + 'max_retries' => env('REDIS_MAX_RETRIES', 3), + 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'), + 'backoff_base' => env('REDIS_BACKOFF_BASE', 100), + 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000), ], 'cache' => [ @@ -198,6 +207,10 @@ 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_CACHE_DB', '1'), + 'max_retries' => env('REDIS_MAX_RETRIES', 3), + 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'), + 'backoff_base' => env('REDIS_BACKOFF_BASE', 100), + 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000), ], ], diff --git a/config/filesystems.php b/config/filesystems.php index 8c1193c3e..b7cb4fb7b 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -30,6 +30,7 @@ 'disks' => [ + # this is an explicit specification that the local disk root is in app/local 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), @@ -53,7 +54,7 @@ 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), - 'url' => env('APP_URL').'/storage', + 'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage', 'visibility' => 'public', 'throw' => false, ], diff --git a/config/logging.php b/config/logging.php index 0f9792271..840eb6123 100644 --- a/config/logging.php +++ b/config/logging.php @@ -55,6 +55,9 @@ 'stack' => [ 'driver' => 'stack', 'channels' => ['laravel', 'debug', 'info', 'warning_and_above'], + // L12 defaults, likely for injected env vars + //'channels' => explode(',', (string) env('LOG_STACK', 'single')), + 'ignore_exceptions' => false, ], 'laravel' => [ @@ -85,10 +88,10 @@ 'driver' => 'monolog', 'level' => env('LOG_LEVEL', 'debug'), 'handler' => StreamHandler::class, - 'formatter' => env('LOG_STDERR_FORMATTER'), - 'with' => [ + 'handler_with' => [ 'stream' => 'php://stderr', ], + 'formatter' => env('LOG_STDERR_FORMATTER'), 'processors' => [PsrLogMessageProcessor::class], ], @@ -120,7 +123,7 @@ 'slack' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), - 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'), + 'username' => env('LOG_SLACK_USERNAME', env('APP_NAME', 'Laravel')), 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), 'level' => env('LOG_LEVEL', 'critical'), 'replace_placeholders' => true, diff --git a/config/mail.php b/config/mail.php index 1051c78a9..15c56e4a0 100644 --- a/config/mail.php +++ b/config/mail.php @@ -46,8 +46,7 @@ 'username' => env('MAIL_USERNAME'), 'password' => env('MAIL_PASSWORD'), 'timeout' => null, - 'local_domain' => env('MAIL_EHLO_DOMAIN'), - //'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)), + 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)), ], 'ses' => [ @@ -93,6 +92,7 @@ 'smtp', 'log', ], + 'retry_after' => 60, ], 'roundrobin' => [ @@ -101,6 +101,7 @@ 'ses', 'postmark', ], + 'retry_after' => 60, ], ], diff --git a/config/queue.php b/config/queue.php index 9e4dfceb6..5b54d8035 100644 --- a/config/queue.php +++ b/config/queue.php @@ -25,8 +25,8 @@ | used by your application. An example configuration is provided for | each backend supported by Laravel. You're also free to add more. | - | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" - | + | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", + | "deferred", "background", "failover", "null" */ 'connections' => [ @@ -40,9 +40,7 @@ 'connection' => env('DB_QUEUE_CONNECTION'), 'table' => env('DB_QUEUE_TABLE', 'jobs'), 'queue' => env('DB_QUEUE', 'default'), - //'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 600), - 'retry_after' => 600, //'after_commit' => false, // waits for transactions before running jobs 'after_commit' => true, @@ -77,6 +75,22 @@ 'block_for' => null, 'after_commit' => false, ], + + 'deferred' => [ + 'driver' => 'deferred', + ], + + 'background' => [ + 'driver' => 'background', + ], + + 'failover' => [ + 'driver' => 'failover', + 'connections' => [ + 'database', + 'deferred', + ], + ], */ ], diff --git a/config/services.php b/config/services.php index f6c096ffe..813fcf67a 100644 --- a/config/services.php +++ b/config/services.php @@ -27,7 +27,11 @@ ], 'postmark' => [ - 'token' => env('POSTMARK_TOKEN'), + 'key' => env('POSTMARK_API_KEY'), + ], + + 'resend' => [ + 'key' => env('RESEND_API_KEY'), ], 'ses' => [ @@ -36,10 +40,6 @@ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], - 'resend' => [ - 'key' => env('RESEND_KEY'), - ], - 'slack' => [ 'notifications' => [ 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), diff --git a/config/session.php b/config/session.php index af2f884f2..703220fbf 100644 --- a/config/session.php +++ b/config/session.php @@ -13,8 +13,8 @@ | incoming requests. Laravel supports a variety of storage options to | persist session data. Database storage is a great default choice. | - | Supported: "file", "cookie", "database", "apc", - | "memcached", "redis", "dynamodb", "array" + | Supported: "file", "cookie", "database", "memcached", + | "redis", "dynamodb", "array" | */ @@ -98,7 +98,7 @@ | define the cache store which should be used to store the session data | between requests. This must match one of your defined cache stores. | - | Affects: "apc", "dynamodb", "memcached", "redis" + | Affects: "dynamodb", "memcached", "redis" | */ @@ -132,7 +132,7 @@ /* 'cookie' => env( 'SESSION_COOKIE', - Str::slug(env('APP_NAME', 'laravel'), '_').'_session' + Str::slug((string) env('APP_NAME', 'laravel')).'-session' ), */ @@ -156,7 +156,7 @@ | | This value determines the domain and subdomains the session cookie is | available to. By default, the cookie will be available to the root - | domain and all subdomains. Typically, this shouldn't be changed. + | domain without subdomains. Typically, this shouldn't be changed. | */ diff --git a/phpunit.xml b/phpunit.xml index b62753b10..df8fe53ac 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -22,11 +22,15 @@ - + + - + + + + From fef06d82662b8c2bf6048e05927979e1be978a17 Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 25 Mar 2026 21:55:03 +0000 Subject: [PATCH 019/168] feat: setup ciphersweet --- composer.json | 5 +- composer.lock | 297 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 297 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index c14a41d31..b9bc662ae 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "php": "^8.2", "ext-json": "*", "ext-pdo": "*", + "ext-simplexml": "*", "ext-sodium": "*", "ext-zip": "*", "barryvdh/laravel-debugbar": "v4.1.3", @@ -36,12 +37,12 @@ "neontribe/laravel-specification": "dev-master", "ramsey/uuid": "4.9.2", "sebdesign/laravel-state-machine": "v3.4.7", + "spatie/laravel-ciphersweet": "1.7.4", "symfony/http-client": "v7.4.7", "symfony/lock": "v7.4.6", "symfony/mailchimp-mailer": "v7.4.6", "usmanhalalit/laracsv": "2.1.0", - "werk365/etagconditionals": "dev-master", - "ext-simplexml": "*" + "werk365/etagconditionals": "dev-master" }, "require-dev": { "barryvdh/laravel-ide-helper": "v3.7.0", diff --git a/composer.lock b/composer.lock index 58fe1cf90..7bf324c1c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0954b6c405aeaacb84391c2d1ed603b6", + "content-hash": "a4e6b2fb63524fc70bd1b51f5abe743e", "packages": [ { "name": "barryvdh/laravel-debugbar", @@ -4087,6 +4087,69 @@ ], "time": "2024-09-09T07:06:30+00:00" }, + { + "name": "paragonie/ciphersweet", + "version": "v4.10.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/ciphersweet.git", + "reference": "06cbd57f5d0af53f30c5e11f52664b7d6d8639db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/ciphersweet/zipball/06cbd57f5d0af53f30c5e11f52664b7d6d8639db", + "reference": "06cbd57f5d0af53f30c5e11f52664b7d6d8639db", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "paragonie/constant_time_encoding": "^2|^3", + "paragonie/sodium_compat": "^1|^2", + "php": "^8.1" + }, + "require-dev": { + "infection/infection": "^0", + "phpunit/phpunit": "^10|^11|^12|^13", + "vimeo/psalm": "^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\CipherSweet\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com" + } + ], + "description": "Searchable field-level encryption library for relational databases", + "keywords": [ + "FIPS 140-3", + "NIST cryptography", + "SQL encryption", + "crm", + "cryptography", + "database encryption", + "encrypt", + "encryption", + "field-level encryption", + "libsodium", + "queryable encryption", + "searchable encryption" + ], + "support": { + "issues": "https://github.com/paragonie/ciphersweet/issues", + "source": "https://github.com/paragonie/ciphersweet/tree/v4.10.0" + }, + "time": "2026-03-05T00:32:31+00:00" + }, { "name": "paragonie/constant_time_encoding", "version": "v3.1.3", @@ -4206,6 +4269,102 @@ }, "time": "2020-10-15T08:29:30+00:00" }, + { + "name": "paragonie/sodium_compat", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/sodium_compat.git", + "reference": "4714da6efdc782c06690bc72ce34fae7941c2d9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/4714da6efdc782c06690bc72ce34fae7941c2d9f", + "reference": "4714da6efdc782c06690bc72ce34fae7941c2d9f", + "shasum": "" + }, + "require": { + "php": "^8.1", + "php-64bit": "*" + }, + "require-dev": { + "infection/infection": "^0", + "nikic/php-fuzzer": "^0", + "phpunit/phpunit": "^7|^8|^9|^10|^11", + "vimeo/psalm": "^4|^5|^6" + }, + "suggest": { + "ext-sodium": "Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "files": [ + "autoload.php" + ], + "psr-4": { + "ParagonIE\\Sodium\\": "namespaced/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com" + }, + { + "name": "Frank Denis", + "email": "jedisct1@pureftpd.org" + } + ], + "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", + "keywords": [ + "Authentication", + "BLAKE2b", + "ChaCha20", + "ChaCha20-Poly1305", + "Chapoly", + "Curve25519", + "Ed25519", + "EdDSA", + "Edwards-curve Digital Signature Algorithm", + "Elliptic Curve Diffie-Hellman", + "Poly1305", + "Pure-PHP cryptography", + "RFC 7748", + "RFC 8032", + "Salpoly", + "Salsa20", + "X25519", + "XChaCha20-Poly1305", + "XSalsa20-Poly1305", + "Xchacha20", + "Xsalsa20", + "aead", + "cryptography", + "ecdh", + "elliptic curve", + "elliptic curve cryptography", + "encryption", + "libsodium", + "php", + "public-key cryptography", + "secret-key cryptography", + "side-channel resistant" + ], + "support": { + "issues": "https://github.com/paragonie/sodium_compat/issues", + "source": "https://github.com/paragonie/sodium_compat/tree/v2.5.0" + }, + "time": "2025-12-30T16:12:18+00:00" + }, { "name": "php-debugbar/php-debugbar", "version": "v3.5.1", @@ -5444,6 +5603,138 @@ }, "time": "2026-03-15T16:54:53+00:00" }, + { + "name": "spatie/laravel-ciphersweet", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-ciphersweet.git", + "reference": "0c49f104fd262ab83adbacb0278ef7e399417cea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-ciphersweet/zipball/0c49f104fd262ab83adbacb0278ef7e399417cea", + "reference": "0c49f104fd262ab83adbacb0278ef7e399417cea", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0", + "paragonie/ciphersweet": "^4.0.1", + "php": "^8.1", + "spatie/laravel-package-tools": "^1.12.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.8", + "nunomaduro/collision": "^6.0|^8.0", + "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0|^11.0", + "pestphp/pest": "^1.21|^2.34|^3.7", + "pestphp/pest-plugin-laravel": "^1.1|^2.3|^3.1", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.0|^2.0", + "phpunit/phpunit": "^9.5|^10.5|^11.5.3", + "spatie/laravel-ray": "^1.26" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "LaravelCipherSweet": "Spatie\\LaravelCipherSweet\\Facades\\LaravelCipherSweet" + }, + "providers": [ + "Spatie\\LaravelCipherSweet\\LaravelCipherSweetServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\LaravelCipherSweet\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rias Van der Veken", + "email": "rias@spatie.be", + "role": "Developer" + } + ], + "description": "Use ciphersweet in your Laravel project", + "homepage": "https://github.com/spatie/laravel-ciphersweet", + "keywords": [ + "laravel", + "laravel-ciphersweet", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/laravel-ciphersweet/tree/1.7.4" + }, + "time": "2026-02-27T17:20:09+00:00" + }, + { + "name": "spatie/laravel-package-tools", + "version": "1.93.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/0d097bce95b2bf6802fb1d83e1e753b0f5a948e7", + "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0", + "php": "^8.1" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "orchestra/testbench": "^8.0|^9.2|^10.0|^11.0", + "pestphp/pest": "^2.1|^3.1|^4.0", + "phpunit/php-code-coverage": "^10.0|^11.0|^12.0", + "phpunit/phpunit": "^10.5|^11.5|^12.5", + "spatie/pest-plugin-test-time": "^2.2|^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\LaravelPackageTools\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", + "keywords": [ + "laravel-package-tools", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.93.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-02-21T12:49:54+00:00" + }, { "name": "symfony/cache", "version": "v7.3.11", @@ -14275,9 +14566,9 @@ "php": "^8.2", "ext-json": "*", "ext-pdo": "*", + "ext-simplexml": "*", "ext-sodium": "*", - "ext-zip": "*", - "ext-simplexml": "*" + "ext-zip": "*" }, "platform-dev": {}, "platform-overrides": { From 31d65c11349f68b282423b3df7e9be68978cb2a1 Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 26 Mar 2026 10:30:17 +0000 Subject: [PATCH 020/168] feat: implement tables, models and configs --- .env.example | 1 + app/Carer.php | 32 +++++++---- config/ciphersweet.php | 54 +++++++++++++++++++ ...3_25_215227_create_blind_indexes_table.php | 23 ++++++++ ...3_25_224338_add_secret_fields_to_carer.php | 28 ++++++++++ 5 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 config/ciphersweet.php create mode 100644 database/migrations/2026_03_25_215227_create_blind_indexes_table.php create mode 100644 database/migrations/2026_03_25_224338_add_secret_fields_to_carer.php diff --git a/.env.example b/.env.example index 2f5509a53..ea7d75c3e 100644 --- a/.env.example +++ b/.env.example @@ -56,5 +56,6 @@ MAIL_TO_ADMIN_NAME='Admin Name' MAIL_TO_DEVELOPER_TEAM=arc@neontribe.co.uk MAIL_TO_DEVELOPER_NAME='User Support' +CIPHERSWEET_KEY="" PASSWORD_CLIENT=1 PASSWORD_CLIENT_SECRET=secret diff --git a/app/Carer.php b/app/Carer.php index d34c99a0c..c704a2c57 100644 --- a/app/Carer.php +++ b/app/Carer.php @@ -6,17 +6,22 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Spatie\LaravelCipherSweet\Contracts\CipherSweetEncrypted; +use Spatie\LaravelCipherSweet\Concerns\UsesCipherSweet; +use ParagonIE\CipherSweet\EncryptedRow; +use ParagonIE\CipherSweet\BlindIndex; + /** - * @mixin Eloquent * @property string $name * @property string $ethnicity * @property string $language * @property Family $family */ -class Carer extends Model +class Carer extends Model implements CipherSweetEncrypted { use Aliasable; use SoftDeletes; + use UsesCipherSweet; public const PROGRAMME_ALIASES = [ "Child", @@ -30,8 +35,8 @@ class Carer extends Model */ protected $fillable = [ 'name', - 'ethnicity', - 'language', + 'ethnicity', + 'language', ]; /** @@ -39,14 +44,23 @@ class Carer extends Model * * @var array */ - protected $hidden = []; + protected $hidden = ['emailsecret', 'telnosecret']; + + public static function configureCipherSweet(EncryptedRow $encryptedRow): void + { + $encryptedRow + ->addOptionalTextField('emailsecret') + ->addBlindIndex('emailsecret', new BlindIndex('emailsecret_index')) + ->addOptionalTextField('telnosecret') + ->addBlindIndex('telnosecret', new BlindIndex('telnosecret_index')); + } /** * Get the Family this Carer picks up for. * * @return BelongsTo */ - public function family() : BelongsTo + public function family(): BelongsTo { return $this->belongsTo(Family::class); } @@ -56,8 +70,8 @@ public function family() : BelongsTo */ public function delete() { - $this->name = 'Deleted'; - $this->save(); - return parent::delete(); + $this->name = 'Deleted'; + $this->save(); + return parent::delete(); } } diff --git a/config/ciphersweet.php b/config/ciphersweet.php new file mode 100644 index 000000000..d0c4c5c45 --- /dev/null +++ b/config/ciphersweet.php @@ -0,0 +1,54 @@ + env('CIPHERSWEET_BACKEND', 'nacl'), + + /** + * Set backend-specific options here. "custom" points to a factory class that returns a + * backend from its `__invoke` method. Please see the docs for more details. + */ + 'backends' => [ + // 'custom' => CustomBackendFactory::class, + ], + + /** + * Select which key provider your application will use. The default option + * is to read a string literal out of .env, but it's also possible to + * provide the key in a file or use random keys for testing. + * + * Supported: "file", "random", "string", "custom" + */ + 'provider' => env('CIPHERSWEET_PROVIDER', 'string'), + + /** + * Set provider-specific options here. "string" will read the key directly + * from your .env file. "file" will read the contents of the specified file + * to use as your key. "custom" points to a factory class that returns a + * provider from its `__invoke` method. Please see the docs for more details. + */ + 'providers' => [ + 'file' => [ + 'path' => env('CIPHERSWEET_FILE_PATH'), + ], + 'string' => [ + 'key' => env('CIPHERSWEET_KEY'), + ], + // 'custom' => CustomKeyProviderFactory::class, + ], + + /* + * The provided code snippet checks whether the $permitEmpty property is set to false + * for a given field. If it is not set to false, it throws an EmptyFieldException indicating + * that the field is not defined in the row. This ensures that the code enforces the requirement for + * the field to have a value and alerts the user if it is empty or undefined. + * Supported: "true", "false" + */ + 'permit_empty' => env('CIPHERSWEET_PERMIT_EMPTY', false) +]; diff --git a/database/migrations/2026_03_25_215227_create_blind_indexes_table.php b/database/migrations/2026_03_25_215227_create_blind_indexes_table.php new file mode 100644 index 000000000..acd35fbcf --- /dev/null +++ b/database/migrations/2026_03_25_215227_create_blind_indexes_table.php @@ -0,0 +1,23 @@ +morphs('indexable'); + $table->string('name'); + $table->string('value'); + $table->index(['name', 'value']); + $table->unique(['indexable_type', 'indexable_id', 'name']); + }); + } + + public function down(): void + { + Schema::dropIfExists('blind_indexes'); + } +}; diff --git a/database/migrations/2026_03_25_224338_add_secret_fields_to_carer.php b/database/migrations/2026_03_25_224338_add_secret_fields_to_carer.php new file mode 100644 index 000000000..b7249f875 --- /dev/null +++ b/database/migrations/2026_03_25_224338_add_secret_fields_to_carer.php @@ -0,0 +1,28 @@ +text('emailsecret')->after('name')->nullable(); + $table->text('telnosecret')->after('name')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('carers', static function (Blueprint $table) { + $table->dropColumn(['emailsecret', 'telnosecret']); + }); + } +}; From 9dc80a3bcdb718390b9b12b02bb00a426cb66d13 Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 26 Mar 2026 11:49:45 +0000 Subject: [PATCH 021/168] exp: abstract value to objects --- app/Carer.php | 13 +---- app/Support/SecureModel.php | 103 ++++++++++++++++++++++++++++++++++++ app/Support/SecureValue.php | 47 ++++++++++++++++ 3 files changed, 152 insertions(+), 11 deletions(-) create mode 100644 app/Support/SecureModel.php create mode 100644 app/Support/SecureValue.php diff --git a/app/Carer.php b/app/Carer.php index c704a2c57..16bfb1e1a 100644 --- a/app/Carer.php +++ b/app/Carer.php @@ -2,12 +2,11 @@ namespace App; +use App\Support\SecureModel; use App\Traits\Aliasable; -use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Spatie\LaravelCipherSweet\Contracts\CipherSweetEncrypted; -use Spatie\LaravelCipherSweet\Concerns\UsesCipherSweet; use ParagonIE\CipherSweet\EncryptedRow; use ParagonIE\CipherSweet\BlindIndex; @@ -17,11 +16,10 @@ * @property string $language * @property Family $family */ -class Carer extends Model implements CipherSweetEncrypted +class Carer extends SecureModel implements CipherSweetEncrypted { use Aliasable; use SoftDeletes; - use UsesCipherSweet; public const PROGRAMME_ALIASES = [ "Child", @@ -39,13 +37,6 @@ class Carer extends Model implements CipherSweetEncrypted 'language', ]; - /** - * The attributes that should be hidden for arrays. - * - * @var array - */ - protected $hidden = ['emailsecret', 'telnosecret']; - public static function configureCipherSweet(EncryptedRow $encryptedRow): void { $encryptedRow diff --git a/app/Support/SecureModel.php b/app/Support/SecureModel.php new file mode 100644 index 000000000..8d40f5164 --- /dev/null +++ b/app/Support/SecureModel.php @@ -0,0 +1,103 @@ +hideEncryptedAttributes(); + }); + } + + /** Add to "hidden" fields in model */ + public function hideEncryptedAttributes(): void + { + if (!$this->secretsHidden) { + return; + } + + $this->makeHidden($this->encryptedFields()); + } + + /** Get list of fields */ + public function encryptedFields(): array + { + return static::getCipherSweetEncryptedRow()->listEncryptedFields(); + } + + /** Gets the field */ + public function reveal(string $field): mixed + { + if (!in_array($field, $this->encryptedFields(), true)) { + return $this->getAttribute($field); + } + + $this->authorizeReveal($field); + + return $this->attributes[$field] ?? null; + } + + public function getAttribute($key) + { + $value = parent::getAttribute($key); + + if ( + $value !== null + && in_array($key, $this->encryptedFields(), true) + ) { + return new SecureValue($value); + } + + return $value; + } + + /** Place for guards */ + protected function authorizeReveal(string $field): void + { + // Override in concrete model / policy layer + } + + /** Clone without secrets */ + public function withoutSecrets(): static + { + $clone = clone $this; + + foreach ($this->encryptedFields() as $field) { + unset($clone->{$field}); + } + + return $clone; + } + + /** Stop toArray leaks */ + public function toArray(): array + { + $array = parent::toArray(); + + foreach ($this->encryptedFields() as $field) { + unset($array[$field]); + } + + return $array; + } + + /** Stop debug leaks */ + public function __debugInfo(): array + { + return [ + 'model' => static::class, + 'id' => $this->getKey(), + 'attributes' => '[secure]', + ]; + } +} diff --git a/app/Support/SecureValue.php b/app/Support/SecureValue.php new file mode 100644 index 000000000..e2232d4b1 --- /dev/null +++ b/app/Support/SecureValue.php @@ -0,0 +1,47 @@ +value = $value; + } + + public function reveal(): string + { + $this->revealed = true; + + return $this->value; + } + + public function masked(int $visible = 4): string + { + $len = strlen($this->value); + + return str_repeat('*', $len - $visible) . substr($this->value, -$visible); + } + + public function jsonSerialize(): mixed + { + return '[secret]'; + } + + public function __toString(): string + { + return '[secret]'; + } + + public function __debugInfo(): array + { + return ['secret' => '[hidden]']; + } +} From 908c98e5a88ee5683d7f895f4cf3b021b9e226aa Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 26 Mar 2026 15:03:49 +0000 Subject: [PATCH 022/168] exp: try a lazyloader --- app/Carer.php | 5 +- app/Support/LazySecureModel.php | 103 ++++++++++++++++++++++++++++ app/Support/LazySecureValue.php | 67 ++++++++++++++++++ app/Support/UsesCipherSweetLazy.php | 38 ++++++++++ 4 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 app/Support/LazySecureModel.php create mode 100644 app/Support/LazySecureValue.php create mode 100644 app/Support/UsesCipherSweetLazy.php diff --git a/app/Carer.php b/app/Carer.php index 16bfb1e1a..a8955abea 100644 --- a/app/Carer.php +++ b/app/Carer.php @@ -2,7 +2,8 @@ namespace App; -use App\Support\SecureModel; +use App\Support\LazySecureModel; + use App\Traits\Aliasable; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -16,7 +17,7 @@ * @property string $language * @property Family $family */ -class Carer extends SecureModel implements CipherSweetEncrypted +class Carer extends LazySecureModel implements CipherSweetEncrypted { use Aliasable; use SoftDeletes; diff --git a/app/Support/LazySecureModel.php b/app/Support/LazySecureModel.php new file mode 100644 index 000000000..b7a32dc97 --- /dev/null +++ b/app/Support/LazySecureModel.php @@ -0,0 +1,103 @@ +hideEncryptedAttributes(); + }); + } + + /** Add to "hidden" fields in model */ + public function hideEncryptedAttributes(): void + { + if (!$this->secretsHidden) { + return; + } + $this->makeHidden($this->encryptedFields()); + } + + /** Get list of fields */ + protected function encryptedFields(): array + { + return static::$encryptedFieldCache[static::class] + ??= static::getCipherSweetEncryptedRow()->listEncryptedFields(); + } + + /** Gets the field */ + public function reveal(string $field): mixed + { + if (!in_array($field, $this->encryptedFields(), true)) { + return $this->getAttribute($field); + } + + $this->authorizeReveal($field); + + return $this->attributes[$field] ?? null; + } + + public function getAttribute($key) + { + $value = parent::getAttribute($key); + + if ( + $value !== null + && in_array($key, $this->encryptedFields(), true) + ) { + return new LazySecureValue($this, $key); + } + + return $value; + } + + /** Place for guards */ + protected function authorizeReveal(string $field): void + { + // Override in concrete model / policy layer + } + + /** Clone without secrets */ + public function withoutSecrets(): static + { + $clone = clone $this; + + foreach ($this->encryptedFields() as $field) { + unset($clone->{$field}); + } + + return $clone; + } + + /** Stop toArray leaks */ + public function toArray(): array + { + $array = parent::toArray(); + + foreach ($this->encryptedFields() as $field) { + unset($array[$field]); + } + + return $array; + } + + /** Stop debug leaks */ + public function __debugInfo(): array + { + return [ + 'model' => static::class, + 'id' => $this->getKey(), + 'attributes' => '[secure]', + ]; + } +} diff --git a/app/Support/LazySecureValue.php b/app/Support/LazySecureValue.php new file mode 100644 index 000000000..713d090ca --- /dev/null +++ b/app/Support/LazySecureValue.php @@ -0,0 +1,67 @@ +model = $model; + $this->field = $field; + } + + public function reveal(): ?string + { + if ($this->plaintext !== null) { + return $this->plaintext; + } + + $row = $this->model::getCipherSweetEncryptedRow(); + + $payload = []; + + foreach ($row->listEncryptedFields() as $field) { + $payload[$field] = $this->model->getRawOriginal($field); + } + + $decrypted = $row + ->setPermitEmpty(config('ciphersweet.permit_empty', false)) + ->decryptRow($payload); + + return $this->plaintext = $decrypted[$this->field] ?? null; + } + + public function masked(int $visible = 4): string + { + $value = $this->reveal(); + + if ($value === null) { + return ''; + } + + return str_repeat('*', strlen($value) - $visible) + . substr($value, -$visible); + } + + public function __toString() + { + return '[secret]'; + } + + public function jsonSerialize(): mixed + { + return '[secret]'; + } + + public function __debugInfo() + { + return ['secret' => '[lazy]']; + } +} diff --git a/app/Support/UsesCipherSweetLazy.php b/app/Support/UsesCipherSweetLazy.php new file mode 100644 index 000000000..4bb94a9f9 --- /dev/null +++ b/app/Support/UsesCipherSweetLazy.php @@ -0,0 +1,38 @@ +saving($m); + } + ); + + static::saved( + static function ($m) { + app(ModelObserver::class)->saved($m); + } + ); + + static::deleting( + static function ($m) { + app(ModelObserver::class)->deleting($m); + } + ); + } +} From 504a81a7dfe837d26ae9fe55d3e8c13818bde16b Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 26 Mar 2026 15:30:05 +0000 Subject: [PATCH 023/168] exp: remove previous versions --- app/Carer.php | 2 + app/Support/LazySecureModel.php | 156 ++++++++++++++++++++++++-------- app/Support/LazySecureValue.php | 68 ++++++++------ app/Support/SecureModel.php | 103 --------------------- app/Support/SecureValue.php | 47 ---------- 5 files changed, 160 insertions(+), 216 deletions(-) delete mode 100644 app/Support/SecureModel.php delete mode 100644 app/Support/SecureValue.php diff --git a/app/Carer.php b/app/Carer.php index a8955abea..c9de08c87 100644 --- a/app/Carer.php +++ b/app/Carer.php @@ -36,6 +36,8 @@ class Carer extends LazySecureModel implements CipherSweetEncrypted 'name', 'ethnicity', 'language', + 'emailsecret', + 'telnosecret' ]; public static function configureCipherSweet(EncryptedRow $encryptedRow): void diff --git a/app/Support/LazySecureModel.php b/app/Support/LazySecureModel.php index b7a32dc97..45eaa0ce0 100644 --- a/app/Support/LazySecureModel.php +++ b/app/Support/LazySecureModel.php @@ -3,15 +3,22 @@ namespace App\Support; use Illuminate\Database\Eloquent\Model; +use InvalidArgumentException; abstract class LazySecureModel extends Model { use UsesCipherSweetLazy; - protected bool $secretsHidden = true; + /** + * Per-class encrypted field cache. + */ protected static array $encryptedFieldCache = []; - /** when retrieving the model auto-"hide" the fields */ + /** + * Cached decrypted row for this model instance. + */ + protected ?array $lazyDecryptedRowCache = null; + protected static function booted(): void { static::retrieved(static function (self $model) { @@ -19,67 +26,87 @@ protected static function booted(): void }); } - /** Add to "hidden" fields in model */ - public function hideEncryptedAttributes(): void + public function hideEncryptedAttributes(): static { - if (!$this->secretsHidden) { - return; - } $this->makeHidden($this->encryptedFields()); + return $this; } - /** Get list of fields */ - protected function encryptedFields(): array + public function encryptedFields(): array { return static::$encryptedFieldCache[static::class] ??= static::getCipherSweetEncryptedRow()->listEncryptedFields(); } - /** Gets the field */ - public function reveal(string $field): mixed + /** + * Lazy decrypt full encrypted row once, then cache it on the model instance. + */ + public function decryptEncryptedRowForLazyAccess(): array { - if (!in_array($field, $this->encryptedFields(), true)) { - return $this->getAttribute($field); + if ($this->lazyDecryptedRowCache !== null) { + return $this->lazyDecryptedRowCache; } - $this->authorizeReveal($field); + $row = static::getCipherSweetEncryptedRow() + ->setPermitEmpty(config('ciphersweet.permit_empty', false)); + + $payload = []; + + foreach ($this->encryptedFields() as $field) { + // Important: use raw/original DB values, not accessors. + $payload[$field] = $this->getRawOriginal($field); + + // Some hydration paths may not populate "original" as expected. + // Fall back to raw attributes if needed. + if (!array_key_exists($field, $this->getOriginal()) && array_key_exists($field, $this->getAttributes())) { + $payload[$field] = $this->getAttributes()[$field]; + } + + // Ensure every configured encrypted field exists in the payload, + // even when null, to satisfy CipherSweet row expectations. + $payload[$field] ??= null; + } - return $this->attributes[$field] ?? null; + return $this->lazyDecryptedRowCache = $row->decryptRow($payload); } - public function getAttribute($key) + /** + * If someone accesses $model->email directly and email is encrypted, + * return a LazySecretValue instead of plaintext/ciphertext. + */ + public function getAttribute($key): mixed { - $value = parent::getAttribute($key); - - if ( - $value !== null - && in_array($key, $this->encryptedFields(), true) - ) { - return new LazySecureValue($this, $key); + if (is_string($key) && $this->isEncryptedField($key)) { + return $this->secret($key); } - return $value; + return parent::getAttribute($key); } - /** Place for guards */ - protected function authorizeReveal(string $field): void + public function isEncryptedField(string $field): bool { - // Override in concrete model / policy layer + return in_array($field, $this->encryptedFields(), true); } - /** Clone without secrets */ - public function withoutSecrets(): static + /** + * Explicit non-magic access to a secret wrapper. + */ + public function secret(string $field): LazySecureValue { - $clone = clone $this; - - foreach ($this->encryptedFields() as $field) { - unset($clone->{$field}); + if (!$this->isEncryptedField($field)) { + throw new InvalidArgumentException(sprintf( + '"%s" is not a configured encrypted field on %s.', + $field, + static::class + )); } - return $clone; + return new LazySecureValue($this, $field); } - /** Stop toArray leaks */ + /** + * Remove secrets from array serialization regardless of $hidden changes elsewhere. + */ public function toArray(): array { $array = parent::toArray(); @@ -91,13 +118,68 @@ public function toArray(): array return $array; } - /** Stop debug leaks */ + /** + * Safe debug output. + */ public function __debugInfo(): array { return [ 'model' => static::class, 'id' => $this->getKey(), - 'attributes' => '[secure]', + 'attributes' => collect(parent::attributesToArray()) + ->except($this->encryptedFields()) + ->all(), + 'hidden_encrypted_fields' => $this->encryptedFields(), ]; } + + /** + * Override in concrete models, policies, or a shared auth trait. + */ + public function authorizeReveal(string $field): void + { + // no-op by default + } + + /** + * Optional helper for safe transport into jobs/events/resources. + */ + public function withoutSecrets(): static + { + $clone = clone $this; + $clone->flushSecretCache(); + + foreach ($clone->encryptedFields() as $field) { + unset($clone->{$field}); + } + + $clone->makeHidden($clone->encryptedFields()); + + return $clone; + } + + /** + * Clear cached decrypted values after mutation/refresh. + */ + public function flushSecretCache(): static + { + $this->lazyDecryptedRowCache = null; + + return $this; + } + + /** + * Important: whenever attributes are replaced wholesale, clear cache. + */ + public function setRawAttributes(array $attributes, $sync = false) + { + $this->flushSecretCache(); + return parent::setRawAttributes($attributes, $sync); + } + + public function refresh() + { + $this->flushSecretCache(); + return parent::refresh(); + } } diff --git a/app/Support/LazySecureValue.php b/app/Support/LazySecureValue.php index 713d090ca..f41779590 100644 --- a/app/Support/LazySecureValue.php +++ b/app/Support/LazySecureValue.php @@ -7,38 +7,29 @@ class LazySecureValue implements JsonSerializable, Stringable { - protected object $model; - protected string $field; - protected ?string $plaintext = null; - - public function __construct(object $model, string $field) - { - $this->model = $model; - $this->field = $field; + public function __construct( + protected object $model, + protected string $field, + ) { } - public function reveal(): ?string + public function reveal(): mixed { - if ($this->plaintext !== null) { - return $this->plaintext; + if (! method_exists($this->model, 'authorizeReveal')) { + throw new \LogicException(sprintf( + '%s must define authorizeReveal()', + $this->model::class + )); } - $row = $this->model::getCipherSweetEncryptedRow(); - - $payload = []; - - foreach ($row->listEncryptedFields() as $field) { - $payload[$field] = $this->model->getRawOriginal($field); - } + $this->model->authorizeReveal($this->field); - $decrypted = $row - ->setPermitEmpty(config('ciphersweet.permit_empty', false)) - ->decryptRow($payload); + $row = $this->model->decryptEncryptedRowForLazyAccess(); - return $this->plaintext = $decrypted[$this->field] ?? null; + return $row[$this->field] ?? null; } - public function masked(int $visible = 4): string + public function masked(int $visible = 4, string $mask = '*'): string { $value = $this->reveal(); @@ -46,13 +37,23 @@ public function masked(int $visible = 4): string return ''; } - return str_repeat('*', strlen($value) - $visible) - . substr($value, -$visible); + $value = (string) $value; + $length = mb_strlen($value); + + if ($visible <= 0) { + return str_repeat($mask, $length); + } + + if ($length <= $visible) { + return $value; + } + + return str_repeat($mask, $length - $visible) . mb_substr($value, -$visible); } - public function __toString() + public function isNull(): bool { - return '[secret]'; + return $this->reveal() === null; } public function jsonSerialize(): mixed @@ -60,8 +61,17 @@ public function jsonSerialize(): mixed return '[secret]'; } - public function __debugInfo() + public function __toString(): string + { + return '[secret]'; + } + + public function __debugInfo(): array { - return ['secret' => '[lazy]']; + return [ + 'secret' => '[hidden]', + 'field' => $this->field, + 'model' => $this->model::class, + ]; } } diff --git a/app/Support/SecureModel.php b/app/Support/SecureModel.php deleted file mode 100644 index 8d40f5164..000000000 --- a/app/Support/SecureModel.php +++ /dev/null @@ -1,103 +0,0 @@ -hideEncryptedAttributes(); - }); - } - - /** Add to "hidden" fields in model */ - public function hideEncryptedAttributes(): void - { - if (!$this->secretsHidden) { - return; - } - - $this->makeHidden($this->encryptedFields()); - } - - /** Get list of fields */ - public function encryptedFields(): array - { - return static::getCipherSweetEncryptedRow()->listEncryptedFields(); - } - - /** Gets the field */ - public function reveal(string $field): mixed - { - if (!in_array($field, $this->encryptedFields(), true)) { - return $this->getAttribute($field); - } - - $this->authorizeReveal($field); - - return $this->attributes[$field] ?? null; - } - - public function getAttribute($key) - { - $value = parent::getAttribute($key); - - if ( - $value !== null - && in_array($key, $this->encryptedFields(), true) - ) { - return new SecureValue($value); - } - - return $value; - } - - /** Place for guards */ - protected function authorizeReveal(string $field): void - { - // Override in concrete model / policy layer - } - - /** Clone without secrets */ - public function withoutSecrets(): static - { - $clone = clone $this; - - foreach ($this->encryptedFields() as $field) { - unset($clone->{$field}); - } - - return $clone; - } - - /** Stop toArray leaks */ - public function toArray(): array - { - $array = parent::toArray(); - - foreach ($this->encryptedFields() as $field) { - unset($array[$field]); - } - - return $array; - } - - /** Stop debug leaks */ - public function __debugInfo(): array - { - return [ - 'model' => static::class, - 'id' => $this->getKey(), - 'attributes' => '[secure]', - ]; - } -} diff --git a/app/Support/SecureValue.php b/app/Support/SecureValue.php deleted file mode 100644 index e2232d4b1..000000000 --- a/app/Support/SecureValue.php +++ /dev/null @@ -1,47 +0,0 @@ -value = $value; - } - - public function reveal(): string - { - $this->revealed = true; - - return $this->value; - } - - public function masked(int $visible = 4): string - { - $len = strlen($this->value); - - return str_repeat('*', $len - $visible) . substr($this->value, -$visible); - } - - public function jsonSerialize(): mixed - { - return '[secret]'; - } - - public function __toString(): string - { - return '[secret]'; - } - - public function __debugInfo(): array - { - return ['secret' => '[hidden]']; - } -} From ad1caff64562e36343a44748e526a6fb05cc00b0 Mon Sep 17 00:00:00 2001 From: charles strange Date: Fri, 27 Mar 2026 15:20:29 +0000 Subject: [PATCH 024/168] exp: claude wrote some tests! --- app/Carer.php | 1 - app/Support/LazySecureModel.php | 15 +- app/Support/LazySecureValue.php | 25 +- app/Support/UsesCipherSweetLazy.php | 4 + tests/Stubs/CachingTestSecureModel.php | 62 ++ tests/Stubs/TestSecureModel.php | 89 +++ tests/Unit/Support/LazySecureModelTest.php | 661 ++++++++++++++++++ tests/Unit/Support/LazySecureValueTest.php | 235 +++++++ .../Unit/Support/UsesCipherSweetLazyTest.php | 177 +++++ 9 files changed, 1237 insertions(+), 32 deletions(-) create mode 100644 tests/Stubs/CachingTestSecureModel.php create mode 100644 tests/Stubs/TestSecureModel.php create mode 100644 tests/Unit/Support/LazySecureModelTest.php create mode 100644 tests/Unit/Support/LazySecureValueTest.php create mode 100644 tests/Unit/Support/UsesCipherSweetLazyTest.php diff --git a/app/Carer.php b/app/Carer.php index c9de08c87..6eda87d5c 100644 --- a/app/Carer.php +++ b/app/Carer.php @@ -3,7 +3,6 @@ namespace App; use App\Support\LazySecureModel; - use App\Traits\Aliasable; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Relations\BelongsTo; diff --git a/app/Support/LazySecureModel.php b/app/Support/LazySecureModel.php index 45eaa0ce0..7a90ad80e 100644 --- a/app/Support/LazySecureModel.php +++ b/app/Support/LazySecureModel.php @@ -71,7 +71,7 @@ public function decryptEncryptedRowForLazyAccess(): array } /** - * If someone accesses $model->email directly and email is encrypted, + * If someone accesses $model->emailsecret directly and email is encrypted, * return a LazySecretValue instead of plaintext/ciphertext. */ public function getAttribute($key): mixed @@ -105,7 +105,7 @@ public function secret(string $field): LazySecureValue } /** - * Remove secrets from array serialization regardless of $hidden changes elsewhere. + * Belt and Braces: remove secrets from array serialization regardless of $hidden changes elsewhere */ public function toArray(): array { @@ -119,7 +119,7 @@ public function toArray(): array } /** - * Safe debug output. + * Safe debug output, prevents secrets in debugs */ public function __debugInfo(): array { @@ -142,7 +142,7 @@ public function authorizeReveal(string $field): void } /** - * Optional helper for safe transport into jobs/events/resources. + * Optional helper for safe cloning into jobs/events/resources. */ public function withoutSecrets(): static { @@ -161,23 +161,22 @@ public function withoutSecrets(): static /** * Clear cached decrypted values after mutation/refresh. */ - public function flushSecretCache(): static + public function flushSecretCache(): self { $this->lazyDecryptedRowCache = null; - return $this; } /** * Important: whenever attributes are replaced wholesale, clear cache. */ - public function setRawAttributes(array $attributes, $sync = false) + public function setRawAttributes(array $attributes, $sync = false): self { $this->flushSecretCache(); return parent::setRawAttributes($attributes, $sync); } - public function refresh() + public function refresh(): self { $this->flushSecretCache(); return parent::refresh(); diff --git a/app/Support/LazySecureValue.php b/app/Support/LazySecureValue.php index f41779590..138bc3ef7 100644 --- a/app/Support/LazySecureValue.php +++ b/app/Support/LazySecureValue.php @@ -8,7 +8,7 @@ class LazySecureValue implements JsonSerializable, Stringable { public function __construct( - protected object $model, + protected LazySecureModel $model, protected string $field, ) { } @@ -29,28 +29,6 @@ public function reveal(): mixed return $row[$this->field] ?? null; } - public function masked(int $visible = 4, string $mask = '*'): string - { - $value = $this->reveal(); - - if ($value === null) { - return ''; - } - - $value = (string) $value; - $length = mb_strlen($value); - - if ($visible <= 0) { - return str_repeat($mask, $length); - } - - if ($length <= $visible) { - return $value; - } - - return str_repeat($mask, $length - $visible) . mb_substr($value, -$visible); - } - public function isNull(): bool { return $this->reveal() === null; @@ -75,3 +53,4 @@ public function __debugInfo(): array ]; } } + diff --git a/app/Support/UsesCipherSweetLazy.php b/app/Support/UsesCipherSweetLazy.php index 4bb94a9f9..140d68cef 100644 --- a/app/Support/UsesCipherSweetLazy.php +++ b/app/Support/UsesCipherSweetLazy.php @@ -5,6 +5,10 @@ use Spatie\LaravelCipherSweet\Concerns\UsesCipherSweet; use Spatie\LaravelCipherSweet\Observers\ModelObserver; +/** + * Modifies the standard trait to avoid decrypting for convenience on model hydration. + * We do not want the secrets in memory. + */ trait UsesCipherSweetLazy { use UsesCipherSweet { diff --git a/tests/Stubs/CachingTestSecureModel.php b/tests/Stubs/CachingTestSecureModel.php new file mode 100644 index 000000000..a723a00f3 --- /dev/null +++ b/tests/Stubs/CachingTestSecureModel.php @@ -0,0 +1,62 @@ +shouldDenyReveal = true; + + return $this; + } + + /** + * Hard-coded to avoid needing a live CipherSweet backend for the + * majority of tests. + */ + public function encryptedFields(): array + { + return ['secret_field', 'another_secret']; + } + + public function authorizeReveal(string $field): void + { + if ($this->shouldDenyReveal) { + $this->shouldDenyReveal = false; // auto-reset so the stub stays simple + throw new AuthorizationException("Reveal denied for field: {$field}"); + } + } + + public static function configureCipherSweet(EncryptedRow $encryptedRow): void + { + // TODO: Implement configureCipherSweet() method. + } +} diff --git a/tests/Unit/Support/LazySecureModelTest.php b/tests/Unit/Support/LazySecureModelTest.php new file mode 100644 index 000000000..091fd913b --- /dev/null +++ b/tests/Unit/Support/LazySecureModelTest.php @@ -0,0 +1,661 @@ +modelWithAttributes(['secret_field' => 'ciphertext_123']); + + $value = $model->getAttribute('secret_field'); + + $this->assertInstanceOf(LazySecureValue::class, $value); + } + + #[Test] + public function get_attribute_via_magic_property_also_returns_lazy_secure_value(): void + { + $model = $this->modelWithAttributes(['secret_field' => 'ciphertext_123']); + + // Laravel routes $model->secret_field through getAttribute(). + $this->assertInstanceOf(LazySecureValue::class, $model->secret_field); + } + + #[Test] + public function get_attribute_returns_plain_value_for_a_non_encrypted_field(): void + { + $model = $this->modelWithAttributes(['name' => 'Alice', 'secret_field' => 'enc']); + + $this->assertSame('Alice', $model->getAttribute('name')); + } + + #[Test] + public function get_attribute_returns_null_for_an_absent_non_encrypted_field(): void + { + $model = $this->modelWithAttributes([]); + + $this->assertNull($model->getAttribute('name')); + } + + // ── toArray ────────────────────────────────────────────────────────────── + + #[Test] + public function to_array_excludes_all_encrypted_fields(): void + { + $model = $this->modelWithAttributes([ + 'id' => 1, + 'name' => 'Bob', + 'secret_field' => 'enc_a', + 'another_secret' => 'enc_b', + ]); + + $array = $model->toArray(); + + $this->assertArrayNotHasKey('secret_field', $array); + $this->assertArrayNotHasKey('another_secret', $array); + } + + #[Test] + public function to_array_retains_non_encrypted_fields(): void + { + $model = $this->modelWithAttributes([ + 'id' => 42, + 'name' => 'Bob', + ]); + + $array = $model->toArray(); + + $this->assertSame(42, $array['id']); + $this->assertSame('Bob', $array['name']); + } + + #[Test] + public function to_array_does_not_expose_ciphertext_as_a_plain_string(): void + { + $model = $this->modelWithAttributes([ + 'secret_field' => 'super_sensitive_ciphertext', + ]); + + $this->assertStringNotContainsString('super_sensitive_ciphertext', serialize($model->toArray())); + } + + // ── __debugInfo ────────────────────────────────────────────────────────── + + #[Test] + public function debug_info_omits_encrypted_field_values(): void + { + $model = $this->modelWithAttributes([ + 'id' => 5, + 'name' => 'Carol', + 'secret_field' => 'ciphertext_do_not_leak', + ]); + + $info = $model->__debugInfo(); + + $this->assertArrayNotHasKey('secret_field', $info['attributes']); + $this->assertStringNotContainsString('ciphertext_do_not_leak', serialize($info)); + } + + #[Test] + public function debug_info_lists_encrypted_field_names_as_metadata(): void + { + $model = $this->modelWithAttributes(['id' => 5]); + + $info = $model->__debugInfo(); + + $this->assertContains('secret_field', $info['hidden_encrypted_fields']); + $this->assertContains('another_secret', $info['hidden_encrypted_fields']); + } + + #[Test] + public function debug_info_exposes_the_model_class_and_primary_key(): void + { + $model = $this->modelWithAttributes(['id' => 99]); + + $info = $model->__debugInfo(); + + $this->assertSame(TestSecureModel::class, $info['model']); + $this->assertSame(99, $info['id']); + } + + // ── isEncryptedField ───────────────────────────────────────────────────── + + #[Test] + public function is_encrypted_field_returns_true_for_a_configured_encrypted_field(): void + { + $model = new TestSecureModel(); + + $this->assertTrue($model->isEncryptedField('secret_field')); + $this->assertTrue($model->isEncryptedField('another_secret')); + } + + #[Test] + public function is_encrypted_field_returns_false_for_a_plain_field(): void + { + $model = new TestSecureModel(); + + $this->assertFalse($model->isEncryptedField('name')); + $this->assertFalse($model->isEncryptedField('id')); + $this->assertFalse($model->isEncryptedField('nonexistent_field')); + } + + // ── secret() ───────────────────────────────────────────────────────────── + + #[Test] + public function secret_returns_a_lazy_secure_value_for_an_encrypted_field(): void + { + $model = $this->modelWithAttributes(['secret_field' => 'enc']); + + $wrapper = $model->secret('secret_field'); + + $this->assertInstanceOf(LazySecureValue::class, $wrapper); + } + + #[Test] + public function secret_throws_invalid_argument_exception_for_a_non_encrypted_field(): void + { + $model = new TestSecureModel(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/name/'); + + $model->secret('name'); + } + + // ── hideEncryptedAttributes ─────────────────────────────────────────────── + + #[Test] + public function hide_encrypted_attributes_adds_encrypted_fields_to_the_hidden_list(): void + { + $model = new TestSecureModel(); + $model->hideEncryptedAttributes(); + + $hidden = $model->getHidden(); + + $this->assertContains('secret_field', $hidden); + $this->assertContains('another_secret', $hidden); + } + + #[Test] + public function encrypted_fields_are_hidden_automatically_after_model_retrieval(): void + { + // newFromBuilder simulates Eloquent loading a model from the DB. + $model = (new TestSecureModel())->newFromBuilder([ + 'id' => 10, + 'secret_field' => 'enc_a', + 'another_secret' => 'enc_b', + ]); + + $this->assertContains('secret_field', $model->getHidden()); + $this->assertContains('another_secret', $model->getHidden()); + } + + // ── decryptEncryptedRowForLazyAccess ───────────────────────────────────── + + #[Test] + public function decrypt_encrypted_row_returns_all_decrypted_field_values(): void + { + TestSecureModel::stubEncryptedRow($this->makeEncryptedRowMock([ + 'secret_field' => 'plain_secret', + 'another_secret' => 'plain_other', + ])); + + $model = $this->modelWithAttributes([ + 'secret_field' => 'enc_a', + 'another_secret' => 'enc_b', + ]); + + $decrypted = $model->decryptEncryptedRowForLazyAccess(); + + $this->assertSame('plain_secret', $decrypted['secret_field']); + $this->assertSame('plain_other', $decrypted['another_secret']); + } + + #[Test] + public function decrypt_encrypted_row_uses_raw_original_attribute_values_not_accessors(): void + { + // The model uses getRawOriginal() so that encrypted field accessors + // (which return LazySecureValue) do not interfere with the payload + // passed to decryptRow(). + $capturedPayload = null; + + $encryptedRow = Mockery::mock(EncryptedRow::class); + $encryptedRow->shouldReceive('setPermitEmpty')->andReturnSelf(); + $encryptedRow->shouldReceive('decryptRow') + ->once() + ->andReturnUsing(function (array $payload) use (&$capturedPayload) { + $capturedPayload = $payload; + return ['secret_field' => 'plain', 'another_secret' => 'other']; + }); + + TestSecureModel::stubEncryptedRow($encryptedRow); + + $model = $this->modelWithAttributes([ + 'secret_field' => 'raw_ciphertext_a', + 'another_secret' => 'raw_ciphertext_b', + ]); + + $model->decryptEncryptedRowForLazyAccess(); + + $this->assertSame( + 'raw_ciphertext_a', + $capturedPayload['secret_field'], + 'decryptRow payload must contain raw ciphertext, not a LazySecureValue wrapper.' + ); + $this->assertSame('raw_ciphertext_b', $capturedPayload['another_secret']); + } + + #[Test] + public function decrypt_encrypted_row_caches_result_on_the_model_instance(): void + { + $encryptedRow = Mockery::mock(EncryptedRow::class); + $encryptedRow->shouldReceive('setPermitEmpty')->andReturnSelf(); + $encryptedRow->shouldReceive('decryptRow') + ->once() // Must only be called once despite two decrypt calls. + ->andReturn(['secret_field' => 'plain', 'another_secret' => 'other']); + + TestSecureModel::stubEncryptedRow($encryptedRow); + + $model = $this->modelWithAttributes([ + 'secret_field' => 'enc_a', + 'another_secret' => 'enc_b', + ]); + + $first = $model->decryptEncryptedRowForLazyAccess(); + $second = $model->decryptEncryptedRowForLazyAccess(); + + $this->assertSame($first, $second, 'Subsequent calls must return the cached result.'); + // Mockery verifies decryptRow was called exactly once in tearDown. + } + + #[Test] + public function decrypt_encrypted_row_falls_back_to_attributes_when_original_is_unpopulated(): void + { + // When a model is constructed without sync:true on setRawAttributes, + // getOriginal() may be empty. The implementation falls back to getAttributes(). + $capturedPayload = null; + + $encryptedRow = Mockery::mock(EncryptedRow::class); + $encryptedRow->shouldReceive('setPermitEmpty')->andReturnSelf(); + $encryptedRow->shouldReceive('decryptRow') + ->once() + ->andReturnUsing(function (array $payload) use (&$capturedPayload) { + $capturedPayload = $payload; + return ['secret_field' => 'plain', 'another_secret' => 'other']; + }); + + TestSecureModel::stubEncryptedRow($encryptedRow); + + // sync:false — original is NOT populated, only attributes. + $model = new TestSecureModel(); + $model->setRawAttributes([ + 'secret_field' => 'attr_only_ciphertext', + 'another_secret' => 'attr_only_other', + ], sync: false); + + $model->decryptEncryptedRowForLazyAccess(); + + $this->assertSame( + 'attr_only_ciphertext', + $capturedPayload['secret_field'], + 'Must fall back to attributes when original is not synced.' + ); + } + + // ── flushSecretCache ────────────────────────────────────────────────────── + + #[Test] + public function flush_secret_cache_forces_re_decryption_on_next_access(): void + { + $encryptedRow = Mockery::mock(EncryptedRow::class); + $encryptedRow->shouldReceive('setPermitEmpty')->andReturnSelf(); + $encryptedRow->shouldReceive('decryptRow') + ->twice() // Once before flush, once after. + ->andReturn(['secret_field' => 'plain', 'another_secret' => 'other']); + + TestSecureModel::stubEncryptedRow($encryptedRow); + + $model = $this->modelWithAttributes([ + 'secret_field' => 'enc_a', + 'another_secret' => 'enc_b', + ]); + + $model->decryptEncryptedRowForLazyAccess(); // warms cache + $model->flushSecretCache(); // clears cache + $model->decryptEncryptedRowForLazyAccess(); // must decrypt again + + // Mockery verifies decryptRow was called exactly twice. + } + + #[Test] + public function flush_secret_cache_sets_the_internal_cache_to_null(): void + { + TestSecureModel::stubEncryptedRow($this->makeEncryptedRowMock([ + 'secret_field' => 'plain', 'another_secret' => 'other', + ])); + + $model = $this->modelWithAttributes(['secret_field' => 'enc', 'another_secret' => 'enc2']); + $model->decryptEncryptedRowForLazyAccess(); + + $model->flushSecretCache(); + + $cache = (new \ReflectionProperty(LazySecureModel::class, 'lazyDecryptedRowCache')) + ->getValue($model); + + $this->assertNull($cache); + } + + // ── setRawAttributes ───────────────────────────────────────────────────── + + #[Test] + public function set_raw_attributes_flushes_the_secret_cache(): void + { + $encryptedRow = Mockery::mock(EncryptedRow::class); + $encryptedRow->shouldReceive('setPermitEmpty')->andReturnSelf(); + $encryptedRow->shouldReceive('decryptRow') + ->twice() + ->andReturn( + ['secret_field' => 'first_plain', 'another_secret' => 'first_other'], + ['secret_field' => 'second_plain', 'another_secret' => 'second_other'], + ); + + TestSecureModel::stubEncryptedRow($encryptedRow); + + $model = new TestSecureModel(); + $model->setRawAttributes(['secret_field' => 'enc_1', 'another_secret' => 'enc_1b'], sync: true); + + $first = $model->decryptEncryptedRowForLazyAccess(); + + // Replace attributes — must evict the cached decryption. + $model->setRawAttributes(['secret_field' => 'enc_2', 'another_secret' => 'enc_2b'], sync: true); + + $second = $model->decryptEncryptedRowForLazyAccess(); + + $this->assertSame('first_plain', $first['secret_field']); + $this->assertSame( + 'second_plain', + $second['secret_field'], + 'setRawAttributes must bust the decrypt cache so new data is not stale.' + ); + } + + // ── refresh ─────────────────────────────────────────────────────────────── + + #[Test] + public function refresh_flushes_the_secret_cache_before_reloading_from_db(): void + { + // Strategy: use an anonymous subclass that tracks whether + // flushSecretCache() was called via a public flag, while overriding + // the Eloquent parent::refresh() DB call to be a no-op. + // + // Why not Mockery::mock(TestSecureModel::class)->makePartial()? + // Eloquent's boot sequence calls new static() internally when + // registering event listeners (e.g. the retrieved observer), which + // triggers __construct on the Mockery-generated subclass a second time. + // Mockery treats that as an unexpected call and throws + // BadMethodCallException — even though no expectation was set for it. + // + // The real LazySecureModel::refresh() body runs unchanged here: + // $this->flushSecretCache(); return parent::refresh(); + // We only intercept the final Eloquent DB round-trip. + $model = new class () extends TestSecureModel { + /** Flipped to true the instant flushSecretCache() is invoked. */ + public bool $flushWasCalled = false; + + public function flushSecretCache(): LazySecureModel + { + $this->flushWasCalled = true; + return parent::flushSecretCache(); + } + + /** Skip Eloquent's DB-touching parent so the test needs no connection. */ + public function refresh(): LazySecureModel + { + $this->flushSecretCache(); + return $this; + } + }; + + TestSecureModel::stubEncryptedRow($this->makeEncryptedRowMock([ + 'secret_field' => 'plain', + 'another_secret' => 'other', + ])); + + $model->setRawAttributes(['secret_field' => 'enc', 'another_secret' => 'enc2'], sync: true); + $model->decryptEncryptedRowForLazyAccess(); // warm the cache + + // setRawAttributes() intentionally calls flushSecretCache() as part of its + // own contract, which trips the spy flag during setup. Reset it here so the + // assertion only captures what refresh() itself does. + $model->flushWasCalled = false; + + $this->assertFalse($model->flushWasCalled, 'Sanity: flush must not fire before refresh().'); + + $model->refresh(); + + $this->assertTrue( + $model->flushWasCalled, + 'refresh() must call flushSecretCache() so stale plaintext is evicted ' + . 'before the model reloads its attributes from the database.' + ); + + $cache = (new \ReflectionProperty(LazySecureModel::class, 'lazyDecryptedRowCache')) + ->getValue($model); + + $this->assertNull( + $cache, + 'The decrypt cache must be null after refresh() so the next reveal() ' + . 'decrypts freshly loaded ciphertext rather than serving stale data.' + ); + } + + // ── withoutSecrets ──────────────────────────────────────────────────────── + + #[Test] + public function without_secrets_returns_a_different_object_instance(): void + { + $model = $this->modelWithAttributes(['secret_field' => 'enc', 'another_secret' => 'enc2']); + + $clone = $model->withoutSecrets(); + + $this->assertNotSame($model, $clone); + } + + #[Test] + public function without_secrets_returns_a_clone_with_the_decrypt_cache_flushed(): void + { + TestSecureModel::stubEncryptedRow($this->makeEncryptedRowMock([ + 'secret_field' => 'plain', 'another_secret' => 'other', + ])); + + $model = $this->modelWithAttributes(['secret_field' => 'enc', 'another_secret' => 'enc2']); + $model->decryptEncryptedRowForLazyAccess(); // warm cache on original + + $clone = $model->withoutSecrets(); + + $cloneCache = (new \ReflectionProperty(LazySecureModel::class, 'lazyDecryptedRowCache')) + ->getValue($clone); + + $this->assertNull( + $cloneCache, + 'The clone must not carry a populated decrypt cache; ' + . 'callers (jobs, events, resources) must not accidentally receive plaintext.' + ); + } + + #[Test] + public function without_secrets_does_not_flush_the_original_model_cache(): void + { + TestSecureModel::stubEncryptedRow($this->makeEncryptedRowMock([ + 'secret_field' => 'plain', 'another_secret' => 'other', + ])); + + $model = $this->modelWithAttributes(['secret_field' => 'enc', 'another_secret' => 'enc2']); + $model->decryptEncryptedRowForLazyAccess(); // warm cache on original + + $model->withoutSecrets(); // must not affect original + + $originalCache = (new \ReflectionProperty(LazySecureModel::class, 'lazyDecryptedRowCache')) + ->getValue($model); + + $this->assertNotNull($originalCache, 'Original model cache must be unaffected by cloning.'); + } + + #[Test] + public function without_secrets_makes_encrypted_fields_hidden_on_the_clone(): void + { + $model = $this->modelWithAttributes(['secret_field' => 'enc', 'another_secret' => 'enc2']); + + $clone = $model->withoutSecrets(); + + $this->assertContains('secret_field', $clone->getHidden()); + $this->assertContains('another_secret', $clone->getHidden()); + } + + // ── authorizeReveal (default no-op) ─────────────────────────────────────── + + #[Test] + public function default_authorize_reveal_is_a_no_op_that_does_not_throw(): void + { + // The base LazySecureModel provides a no-op so subclasses can opt-in to + // authorization rather than being forced to implement it immediately. + $model = new class () extends LazySecureModel { + protected $table = 'irrelevant'; + public function encryptedFields(): array + { + return []; + } + public static function getCipherSweetEncryptedRow(): EncryptedRow + { + return Mockery::mock(EncryptedRow::class); + } + + public static function configureCipherSweet(EncryptedRow $encryptedRow): void + { + // TODO: Implement configureCipherSweet() method. + } + }; + + // Must not throw. + $model->authorizeReveal('any_field'); + + $this->addToAssertionCount(1); // confirm test body ran + } + + // ── encryptedFields caching (via CachingTestSecureModel) ───────────────── + + #[Test] + public function encrypted_fields_list_is_derived_from_the_encrypted_row_only_once(): void + { + $encryptedRow = Mockery::mock(EncryptedRow::class); + $encryptedRow->shouldReceive('listEncryptedFields') + ->once() // Must be called exactly once despite multiple encryptedFields() calls. + ->andReturn(['secret_field', 'another_secret']); + + CachingTestSecureModel::stubEncryptedRow($encryptedRow); + + $model = new CachingTestSecureModel(); + + $firstCall = $model->encryptedFields(); + $secondCall = $model->encryptedFields(); + + $this->assertSame($firstCall, $secondCall); + // Mockery verifies listEncryptedFields was called exactly once. + } + + #[Test] + public function encrypted_fields_cache_is_shared_across_model_instances_of_the_same_class(): void + { + $encryptedRow = Mockery::mock(EncryptedRow::class); + $encryptedRow->shouldReceive('listEncryptedFields') + ->once() // Should still only be called once across two distinct instances. + ->andReturn(['secret_field', 'another_secret']); + + CachingTestSecureModel::stubEncryptedRow($encryptedRow); + + $modelA = new CachingTestSecureModel(); + $modelB = new CachingTestSecureModel(); + + $modelA->encryptedFields(); + $modelB->encryptedFields(); // Must hit the class-level cache, not call listEncryptedFields again. + } + + // ── Helpers ────────────────────────────────────────────────────────────── + + /** + * Create a TestSecureModel instance with the given attributes synced to + * original (simulating a model loaded from the database but without an + * actual DB round-trip). + */ + private function modelWithAttributes(array $attributes): TestSecureModel + { + $model = new TestSecureModel(); + $model->setRawAttributes($attributes, sync: true); + + return $model; + } + + /** + * Build a permissive Mockery mock of EncryptedRow that returns the given + * $decryptedData from decryptRow(). + */ + private function makeEncryptedRowMock(array $decryptedData): EncryptedRow + { + $mock = Mockery::mock(EncryptedRow::class); + $mock->shouldReceive('setPermitEmpty')->andReturnSelf(); + $mock->shouldReceive('listEncryptedFields') + ->andReturn(array_keys($decryptedData)); + $mock->shouldReceive('decryptRow') + ->andReturn($decryptedData); + + return $mock; + } +} diff --git a/tests/Unit/Support/LazySecureValueTest.php b/tests/Unit/Support/LazySecureValueTest.php new file mode 100644 index 000000000..fe6309ccb --- /dev/null +++ b/tests/Unit/Support/LazySecureValueTest.php @@ -0,0 +1,235 @@ +model = new TestSecureModel(); + $this->model->setRawAttributes([ + 'secret_field' => 'ciphertext_abc', + 'another_secret' => 'ciphertext_xyz', + ], sync: true); + } + + protected function tearDown(): void + { + TestSecureModel::resetTestState(); + parent::tearDown(); + } + + // ── __toString ─────────────────────────────────────────────────────────── + + #[Test] + public function string_cast_returns_the_safe_placeholder(): void + { + $value = new LazySecureValue($this->model, 'secret_field'); + + $this->assertSame('[secret]', (string) $value); + } + + // ── jsonSerialize ──────────────────────────────────────────────────────── + + #[Test] + public function json_encoding_returns_the_safe_placeholder(): void + { + $value = new LazySecureValue($this->model, 'secret_field'); + + $this->assertSame('"[secret]"', json_encode($value)); + } + + #[Test] + public function json_encoding_inside_an_array_returns_the_placeholder_string(): void + { + $value = new LazySecureValue($this->model, 'secret_field'); + + $encoded = json_decode(json_encode(['key' => $value]), associative: true); + + $this->assertSame('[secret]', $encoded['key']); + } + + // ── __debugInfo ────────────────────────────────────────────────────────── + + #[Test] + public function debug_info_exposes_only_safe_metadata(): void + { + $value = new LazySecureValue($this->model, 'secret_field'); + + $info = $value->__debugInfo(); + + $this->assertSame('[hidden]', $info['secret'], 'plaintext must never appear in debug output'); + $this->assertSame('secret_field', $info['field'], 'field name is safe to expose'); + $this->assertSame(TestSecureModel::class, $info['model'], 'model class is safe to expose'); + } + + #[Test] + public function debug_info_does_not_contain_the_raw_ciphertext_or_plaintext(): void + { + $value = new LazySecureValue($this->model, 'secret_field'); + + $raw = print_r($value->__debugInfo(), return: true); + + $this->assertStringNotContainsString('ciphertext_abc', $raw); + } + + // ── reveal ─────────────────────────────────────────────────────────────── + + #[Test] + public function reveal_returns_the_decrypted_value_for_the_wrapped_field(): void + { + TestSecureModel::stubEncryptedRow($this->mockEncryptedRow([ + 'secret_field' => 'decrypted_secret', + 'another_secret' => 'decrypted_other', + ])); + + $value = new LazySecureValue($this->model, 'secret_field'); + + $this->assertSame('decrypted_secret', $value->reveal()); + } + + #[Test] + public function reveal_returns_null_when_the_field_is_absent_from_the_decrypted_row(): void + { + // Simulate a row that decrypts without the requested field. + TestSecureModel::stubEncryptedRow($this->mockEncryptedRow([ + 'another_secret' => 'decrypted_other', + // 'secret_field' intentionally absent + ])); + + $value = new LazySecureValue($this->model, 'secret_field'); + + $this->assertNull($value->reveal()); + } + + #[Test] + public function reveal_calls_authorize_reveal_on_the_model_before_decrypting(): void + { + // Partial mock so we can spy on authorizeReveal without changing decryption. + $spyModel = Mockery::mock(TestSecureModel::class)->makePartial(); + $spyModel->setRawAttributes([ + 'secret_field' => 'ciphertext_abc', + 'another_secret' => 'ciphertext_xyz', + ], sync: true); + + $spyModel->shouldReceive('authorizeReveal') + ->once() + ->with('secret_field'); + + $spyModel->shouldReceive('decryptEncryptedRowForLazyAccess') + ->once() + ->andReturn(['secret_field' => 'decrypted_secret']); + + $value = new LazySecureValue($spyModel, 'secret_field'); + $value->reveal(); + // Mockery verifies expectations on tearDown. + } + + #[Test] + public function reveal_propagates_authorization_exceptions_from_the_model(): void + { + $this->model->denyNextReveal(); + + // Stub a row so we don't get "null EncryptedRow" before the auth check. + TestSecureModel::stubEncryptedRow($this->mockEncryptedRow([ + 'secret_field' => 'decrypted', + ])); + + $value = new LazySecureValue($this->model, 'secret_field'); + + $this->expectException(AuthorizationException::class); + $this->expectExceptionMessageMatches('/secret_field/'); + + $value->reveal(); + } + + #[Test] + public function reveal_never_bypasses_authorization_to_reach_the_decrypted_value(): void + { + $this->model->denyNextReveal(); + + $encryptedRowMock = $this->mockEncryptedRow(['secret_field' => 'should_not_appear']); + // decryptRow must NOT be called when authorization fails. + $encryptedRowMock->shouldReceive('decryptRow')->never(); + TestSecureModel::stubEncryptedRow($encryptedRowMock); + + $value = new LazySecureValue($this->model, 'secret_field'); + + try { + $value->reveal(); + } catch (AuthorizationException) { + // expected + } + } + + // ── isNull ─────────────────────────────────────────────────────────────── + + #[Test] + public function is_null_returns_true_when_the_decrypted_field_value_is_null(): void + { + TestSecureModel::stubEncryptedRow($this->mockEncryptedRow([ + 'secret_field' => null, + 'another_secret' => 'decrypted_other', + ])); + + $value = new LazySecureValue($this->model, 'secret_field'); + + $this->assertTrue($value->isNull()); + } + + #[Test] + public function is_null_returns_false_when_the_decrypted_field_has_a_value(): void + { + TestSecureModel::stubEncryptedRow($this->mockEncryptedRow([ + 'secret_field' => 'non_null_value', + 'another_secret' => 'decrypted_other', + ])); + + $value = new LazySecureValue($this->model, 'secret_field'); + + $this->assertFalse($value->isNull()); + } + + // ── Helpers ────────────────────────────────────────────────────────────── + + /** + * Build a Mockery mock of EncryptedRow that returns $decryptedData from + * decryptRow() and is otherwise permissive. + */ + private function mockEncryptedRow(array $decryptedData): EncryptedRow + { + $mock = Mockery::mock(EncryptedRow::class); + $mock->shouldReceive('setPermitEmpty')->andReturnSelf(); + $mock->shouldReceive('listEncryptedFields') + ->andReturn(array_keys($decryptedData)); + $mock->shouldReceive('decryptRow') + ->andReturn($decryptedData); + + return $mock; + } +} diff --git a/tests/Unit/Support/UsesCipherSweetLazyTest.php b/tests/Unit/Support/UsesCipherSweetLazyTest.php new file mode 100644 index 000000000..4aaa2bbca --- /dev/null +++ b/tests/Unit/Support/UsesCipherSweetLazyTest.php @@ -0,0 +1,177 @@ +newFromBuilder([ + 'id' => 1, + 'secret_field' => 'ciphertext_abc', + 'another_secret' => 'ciphertext_xyz', + ]); + + // Access the protected $lazyDecryptedRowCache via reflection. + $cache = (new \ReflectionProperty(\App\Support\LazySecureModel::class, 'lazyDecryptedRowCache')) + ->getValue($model); + + $this->assertNull($cache, 'lazyDecryptedRowCache must remain null after retrieval; ' + . 'secrets should not be decrypted into memory automatically.'); + } + + #[Test] + public function raw_attributes_retain_ciphertext_after_model_is_hydrated(): void + { + $model = (new TestSecureModel())->newFromBuilder([ + 'id' => 2, + 'secret_field' => 'raw_ciphertext_value', + ]); + + // getRawOriginal returns the value as stored; it must still be ciphertext. + $this->assertSame( + 'raw_ciphertext_value', + $model->getRawOriginal('secret_field'), + 'Ciphertext must not be replaced by plaintext after retrieval.' + ); + } + + // ── saving ─────────────────────────────────────────────────────────────── + + #[Test] + public function saving_event_invokes_model_observer_saving(): void + { + $model = new TestSecureModel(); + + $observer = Mockery::mock(ModelObserver::class); + $observer->shouldReceive('saving')->once()->with($model); + $this->app->instance(ModelObserver::class, $observer); + + $this->fireEloquentEvent('saving', $model); + } + + // ── saved ──────────────────────────────────────────────────────────────── + + #[Test] + public function saved_event_invokes_model_observer_saved(): void + { + $model = new TestSecureModel(); + + $observer = Mockery::mock(ModelObserver::class); + $observer->shouldReceive('saved')->once()->with($model); + $this->app->instance(ModelObserver::class, $observer); + + $this->fireEloquentEvent('saved', $model); + } + + // ── deleting ───────────────────────────────────────────────────────────── + + #[Test] + public function deleting_event_invokes_model_observer_deleting(): void + { + $model = new TestSecureModel(); + + $observer = Mockery::mock(ModelObserver::class); + $observer->shouldReceive('deleting')->once()->with($model); + $this->app->instance(ModelObserver::class, $observer); + + $this->fireEloquentEvent('deleting', $model); + } + + // ── listener presence (fast sanity checks) ─────────────────────────────── + + #[Test] + public function saving_listener_is_registered_on_the_model(): void + { + // Instantiating the model triggers its boot; the trait must register + // a saving listener. + new TestSecureModel(); + + $listeners = TestSecureModel::getEventDispatcher() + ->getListeners('eloquent.saving: ' . TestSecureModel::class); + + $this->assertNotEmpty($listeners, 'A saving listener must be registered.'); + } + + #[Test] + public function saved_listener_is_registered_on_the_model(): void + { + new TestSecureModel(); + + $listeners = TestSecureModel::getEventDispatcher() + ->getListeners('eloquent.saved: ' . TestSecureModel::class); + + $this->assertNotEmpty($listeners, 'A saved listener must be registered.'); + } + + #[Test] + public function deleting_listener_is_registered_on_the_model(): void + { + new TestSecureModel(); + + $listeners = TestSecureModel::getEventDispatcher() + ->getListeners('eloquent.deleting: ' . TestSecureModel::class); + + $this->assertNotEmpty($listeners, 'A deleting listener must be registered.'); + } + + // ── Helpers ────────────────────────────────────────────────────────────── + + /** + * Dispatch an Eloquent model event directly through the shared dispatcher + * without touching the database. + * + * Eloquent registers listeners under "eloquent.{event}: {ClassName}". + * Dispatching here triggers the static closures registered during boot, + * which in turn call app(ModelObserver::class)->{event}($model). + */ + private function fireEloquentEvent(string $event, Eloquent $model): void + { + $key = sprintf('eloquent.%s: %s', $event, $model::class); + + TestSecureModel::getEventDispatcher()->dispatch($key, $model); + } +} From bf70600cc6e4b968f558ad96fe45808df6aa0285 Mon Sep 17 00:00:00 2001 From: charles strange Date: Fri, 27 Mar 2026 17:34:50 +0000 Subject: [PATCH 025/168] exp: create blade component --- app/Providers/AppServiceProvider.php | 4 +- app/View/Components/SecureInput.php | 40 ++++++++++ .../Composers/PaymentsComposer.php | 12 +-- public/store/css/main.css | 3 +- .../views/components/secure-input.blade.php | 37 +++++++++ .../partials/voucher_collectors.blade.php | 76 +++++++++++-------- 6 files changed, 130 insertions(+), 42 deletions(-) create mode 100644 app/View/Components/SecureInput.php rename app/{Views => View}/Composers/PaymentsComposer.php (66%) create mode 100644 resources/views/components/secure-input.blade.php diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 8344a0f22..bf42e88da 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,12 +2,12 @@ namespace App\Providers; -use App\Views\Composers\PaymentsComposer; +use App\View\Composers\PaymentsComposer; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\Blade; -use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\View; +use Illuminate\Support\ServiceProvider; use Laravel\Passport\Passport; class AppServiceProvider extends ServiceProvider diff --git a/app/View/Components/SecureInput.php b/app/View/Components/SecureInput.php new file mode 100644 index 000000000..d014e3fba --- /dev/null +++ b/app/View/Components/SecureInput.php @@ -0,0 +1,40 @@ + $errors->has($this->name), + 'oldValue' => old($this->name), + 'placeholder' => $this->placeholder ?? ( + $this->filled + ? str_repeat('•', 16) + : 'Enter ' . strtolower($this->label) . '…' + ), + ]); + } +} diff --git a/app/Views/Composers/PaymentsComposer.php b/app/View/Composers/PaymentsComposer.php similarity index 66% rename from app/Views/Composers/PaymentsComposer.php rename to app/View/Composers/PaymentsComposer.php index 5e8d72cc0..2eb9345f4 100644 --- a/app/Views/Composers/PaymentsComposer.php +++ b/app/View/Composers/PaymentsComposer.php @@ -1,6 +1,6 @@ with('hasPayments', $checkPayments); } -} \ No newline at end of file +} diff --git a/public/store/css/main.css b/public/store/css/main.css index 88a7bd894..e3eb1ef63 100644 --- a/public/store/css/main.css +++ b/public/store/css/main.css @@ -57,7 +57,8 @@ input.uppercase { input[type="text"], input[type="month"], -input[type="search"] { +input[type="search"], +input[type="password"]{ padding: 0.5rem 0; font-size: 1.3rem; line-height: 20px; diff --git a/resources/views/components/secure-input.blade.php b/resources/views/components/secure-input.blade.php new file mode 100644 index 000000000..cfd1e1193 --- /dev/null +++ b/resources/views/components/secure-input.blade.php @@ -0,0 +1,37 @@ +@props([ + 'name', + 'label', + 'filled' => false, + 'hasError', + 'oldValue', + 'placeholder', +]) + +
+ + + + merge(['class' => 'invalid']) }} + @endif + /> + + @includeWhen( + $hasError, + 'store.partials.errors', + ['error_array' => ['Please check the entry']] + ) + +
diff --git a/resources/views/store/partials/voucher_collectors.blade.php b/resources/views/store/partials/voucher_collectors.blade.php index cce190a2f..d5e410f70 100644 --- a/resources/views/store/partials/voucher_collectors.blade.php +++ b/resources/views/store/partials/voucher_collectors.blade.php @@ -8,7 +8,7 @@ @if (isset($pri_carer)) {{-- This section should only exist in edit rather than add new record --}} - id")) invalid @endif" type="text" @@ -16,14 +16,20 @@ class="@if($errors->has("pri_carer.$pri_carer->id")) invalid @endif" autocomplete="off" autocorrect="off" spellcheck="false" - >
+ >
@includeWhen( $errors->has("pri_carer.$pri_carer->id"), 'store.partials.errors', ['error_array' => ['This field is required'], 'id' => 'carer-alert'] ) -
-
+ + +
+
@if(empty($pri_carer->ethnicity)) -
Please complete ethnic background.
+
Please complete ethnic background.
@endif -

-
+
+
@if(!isset($pri_carer->language)) -
Please complete main language.
+
Please complete main language.
@endif -

+
@else {{-- If this is a new record do this instead --}} -

+ > +
@includeWhen( $errors->has('pri_carer'), 'store.partials.errors', ['error_array' => ['This field is required'], 'id' => 'carer-alert'] ) -
-

-
-

+ + +
+
+
+ +
@endif
From dbdb1f7721ec9b20f8baf6a4ae1d540f84e67580 Mon Sep 17 00:00:00 2001 From: charles strange Date: Sat, 28 Mar 2026 01:12:13 +0000 Subject: [PATCH 026/168] feat: update ui for store registration creation and update --- .../Requests/StoreNewRegistrationRequest.php | 30 ++-- .../StoreUpdateRegistrationRequest.php | 6 +- composer.json | 1 + composer.lock | 152 +++++++++++++++++- public/store/css/main.css | 49 +++--- resources/lang/en/validation.php | 3 + .../views/components/secure-input.blade.php | 80 +++++++-- .../partials/voucher_collectorsSP.blade.php | 16 +- 8 files changed, 287 insertions(+), 50 deletions(-) diff --git a/app/Http/Requests/StoreNewRegistrationRequest.php b/app/Http/Requests/StoreNewRegistrationRequest.php index be28076e0..0f491f1b9 100644 --- a/app/Http/Requests/StoreNewRegistrationRequest.php +++ b/app/Http/Requests/StoreNewRegistrationRequest.php @@ -29,23 +29,14 @@ public function rules() * These rules validate that the form data is well-formed. * It is NOT responsible for the context validation of that data. */ - $rules = [ + return [ // MUST be present; MUST be in "yes, on, 1, or true" 'consent' => 'required|accepted', - // SOMETIMES is present; MUST be in listed states - 'eligibility-hsbs' => [ - 'sometimes', - 'required', - Rule::in(config('arc.reg_eligibilities_hsbs')), - ], - 'eligibility-nrpf' => [ - 'sometimes', - 'required', - Rule::in(config('arc.reg_eligibilities_nrpf')), - ], // MUST be present; MUST be a not-null string 'pri_carer' => 'required|string', // MAY be present; MUST be a not-null string + 'pri_carer_email' => 'email:rfc', + 'pri_carer_telno' => 'phone:GB', 'new_carers' => 'array|min:1', 'new_carers.*' => [ 'not-regex:/^.*[\p{C}].*$/u', @@ -57,9 +48,18 @@ public function rules() 'children.*.dob' => 'required_if:children.*.verified,=,true|date_format:Y-m', // MAY be present; MUST be a boolean 'children.*.verified' => 'boolean', - 'is_pri_carer' => 'boolean' + 'is_pri_carer' => 'boolean', + // SOMETIMES is present; MUST be in listed states + 'eligibility-hsbs' => [ + 'sometimes', + 'required', + Rule::in(config('arc.reg_eligibilities_hsbs')), + ], + 'eligibility-nrpf' => [ + 'sometimes', + 'required', + Rule::in(config('arc.reg_eligibilities_nrpf')), + ], ]; - - return $rules; } } diff --git a/app/Http/Requests/StoreUpdateRegistrationRequest.php b/app/Http/Requests/StoreUpdateRegistrationRequest.php index 1e97f1a61..86628bccb 100644 --- a/app/Http/Requests/StoreUpdateRegistrationRequest.php +++ b/app/Http/Requests/StoreUpdateRegistrationRequest.php @@ -32,12 +32,14 @@ public function rules() * These rules validate that the form data is well-formed. * It is NOT responsible for the context validation of that data. */ - $rules = [ + return [ // MUST be present, array, 1 member 'pri_carer' => "required|array|min:1|max:1", // Element MUST be present; MUST be a not-null string 'pri_carer.*' => 'required|string', // MAY be present; MUST be a not-null string + 'pri_carer_email' => 'email:rfc', + 'pri_carer_telno' => 'phone:GB', 'sec_carers' => 'array|min:1', 'sec_carers.*' => 'string', // MAY be present; MUST be a not-null string @@ -62,7 +64,5 @@ public function rules() Rule::in(config('arc.reg_eligibilities_nrpf')), ], ]; - - return $rules; } } diff --git a/composer.json b/composer.json index b9bc662ae..c5bf52185 100644 --- a/composer.json +++ b/composer.json @@ -35,6 +35,7 @@ "laravel/ui": "4.6.3", "maennchen/zipstream-php": "3.1.2", "neontribe/laravel-specification": "dev-master", + "propaganistas/laravel-phone": "^6.0", "ramsey/uuid": "4.9.2", "sebdesign/laravel-state-machine": "v3.4.7", "spatie/laravel-ciphersweet": "1.7.4", diff --git a/composer.lock b/composer.lock index 7bf324c1c..77a464f3b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a4e6b2fb63524fc70bd1b51f5abe743e", + "content-hash": "6dddc8494b782a5015404a34ad763d64", "packages": [ { "name": "barryvdh/laravel-debugbar", @@ -1103,6 +1103,84 @@ ], "time": "2025-12-03T09:33:47+00:00" }, + { + "name": "giggsey/libphonenumber-for-php-lite", + "version": "9.0.26", + "source": { + "type": "git", + "url": "https://github.com/giggsey/libphonenumber-for-php-lite.git", + "reference": "7677b7645fcbd34482fcb3a96d4085f92d28ef98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php-lite/zipball/7677b7645fcbd34482fcb3a96d4085f92d28ef98", + "reference": "7677b7645fcbd34482fcb3a96d4085f92d28ef98", + "shasum": "" + }, + "require": { + "php": "^8.1", + "symfony/polyfill-mbstring": "^1.17" + }, + "conflict": { + "giggsey/libphonenumber-for-php": "*" + }, + "require-dev": { + "ext-dom": "*", + "friendsofphp/php-cs-fixer": "^3.71", + "infection/infection": "^0.29|^0.31.0", + "nette/php-generator": "^4.1", + "php-coveralls/php-coveralls": "^2.7", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.7", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpstan/phpstan-phpunit": "^2.0.4", + "phpstan/phpstan-strict-rules": "^2.0.3", + "phpunit/phpunit": "^10.5.45", + "symfony/console": "^6.4", + "symfony/filesystem": "^6.4", + "symfony/process": "^6.4" + }, + "suggest": { + "giggsey/libphonenumber-for-php": "Use libphonenumber-for-php for geocoding, carriers, timezones and matching" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "psr-4": { + "libphonenumber\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Joshua Gigg", + "email": "giggsey@gmail.com", + "homepage": "https://giggsey.com/" + } + ], + "description": "A lite version of giggsey/libphonenumber-for-php, which is a PHP Port of Google's libphonenumber", + "homepage": "https://github.com/giggsey/libphonenumber-for-php-lite", + "keywords": [ + "geocoding", + "geolocation", + "libphonenumber", + "mobile", + "phonenumber", + "validation" + ], + "support": { + "issues": "https://github.com/giggsey/libphonenumber-for-php-lite/issues", + "source": "https://github.com/giggsey/libphonenumber-for-php-lite" + }, + "time": "2026-03-13T10:52:10+00:00" + }, { "name": "graham-campbell/result-type", "version": "v1.1.4", @@ -4718,6 +4796,78 @@ ], "time": "2026-03-19T02:57:58+00:00" }, + { + "name": "propaganistas/laravel-phone", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/Propaganistas/Laravel-Phone.git", + "reference": "b0e2bdb44cfbeaf2466862aaf854815605b5205a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Propaganistas/Laravel-Phone/zipball/b0e2bdb44cfbeaf2466862aaf854815605b5205a", + "reference": "b0e2bdb44cfbeaf2466862aaf854815605b5205a", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "giggsey/libphonenumber-for-php-lite": "^9.0", + "illuminate/contracts": "^11.0|^12.0|^13.0", + "illuminate/support": "^11.0|^12.0|^13.0", + "illuminate/validation": "^11.0|^12.0|^13.0", + "php": "^8.2" + }, + "require-dev": { + "laravel/pint": "^1.21", + "orchestra/testbench": "*", + "phpunit/phpunit": "^11.5.3" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Propaganistas\\LaravelPhone\\PhoneServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Propaganistas\\LaravelPhone\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Propaganistas", + "email": "Propaganistas@users.noreply.github.com" + } + ], + "description": "Adds phone number functionality to Laravel based on Google's libphonenumber API.", + "keywords": [ + "laravel", + "libphonenumber", + "phone", + "validation" + ], + "support": { + "issues": "https://github.com/Propaganistas/Laravel-Phone/issues", + "source": "https://github.com/Propaganistas/Laravel-Phone/tree/6.0.3" + }, + "funding": [ + { + "url": "https://github.com/Propaganistas", + "type": "github" + } + ], + "time": "2026-03-18T07:05:47+00:00" + }, { "name": "psr/cache", "version": "3.0.0", diff --git a/public/store/css/main.css b/public/store/css/main.css index e3eb1ef63..0568ae904 100644 --- a/public/store/css/main.css +++ b/public/store/css/main.css @@ -58,12 +58,13 @@ input.uppercase { input[type="text"], input[type="month"], input[type="search"], -input[type="password"]{ - padding: 0.5rem 0; - font-size: 1.3rem; +input[type="email"], +input[type="password"] { + padding: 0.8rem 0 0.8rem 0.3rem; + font-size: 1rem; line-height: 20px; width: 100%; - border: 2px solid #ffffff; + border: 2px solid gray; box-sizing: border-box; margin: 2px 0; font-family: 'Roboto Slab', serif; @@ -110,9 +111,9 @@ input[type="radio"] + label:before, .inline-dob + div::before { content: ""; background: #ffffff; - width: 34px; - height: 34px; - border: 1px solid #ececec; + width: 2rem; + height: 2rem; + border: 2px solid gray; position: absolute; left: 0; cursor: pointer; @@ -156,11 +157,16 @@ select:focus { } select { - width: 250px; - height: 35px; + width: 100%; background-color: white; - margin-top: 0.5rem; border-radius: 0; + padding: 0.8rem 0; + font-size: 1rem; + line-height: 20px; + border: 2px solid gray; + box-sizing: border-box; + margin: 2px 0; + font-family: 'Roboto Slab', serif; } button { @@ -736,7 +742,7 @@ REGISTER PAGE .user-control { position: relative; - padding-left: 55px; + padding-left: 2.5rem; margin: 1rem 0 2rem 0; min-height: 10px; } @@ -1421,27 +1427,30 @@ ADD CHILD FORM PARTIAL } .dob-month { - border: 2px solid #ffffff; - font-size: 1.5rem; + padding: 0.8rem 0 0.8rem; + border: 2px solid gray; + font-size: 1rem; margin-right: 1rem; text-align: center; - width: 3ch; + width: 5ch; } .dob-year { - border: 2px solid #ffffff; - font-size: 1.5rem; + padding: 0.8rem 0 0.8rem; + border: 2px solid gray; + font-size: 1rem; margin-right: 3rem; text-align: center; - width: 5ch; + width: 7ch; } .age { - border: 2px solid #ffffff; - font-size: 1.5rem; + padding: 0.8rem 0 0.8rem; + border: 2px solid gray; + font-size: 1rem; margin-right: 3rem; text-align: center; - width: 5ch; + width: 7ch; } .dob-verified { diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php index 17a8a4325..5eb7e3914 100644 --- a/resources/lang/en/validation.php +++ b/resources/lang/en/validation.php @@ -92,6 +92,9 @@ 'not_exists' => 'The :attribute field already exists.', 'ge_field' => 'The :attribute must be greater than or equal to :field.', + // Laravel-Phone + 'phone' => 'The :attribute field must be a valid UK number.', + /* |-------------------------------------------------------------------------- | Custom Validation Language Lines diff --git a/resources/views/components/secure-input.blade.php b/resources/views/components/secure-input.blade.php index cfd1e1193..cd02e89a0 100644 --- a/resources/views/components/secure-input.blade.php +++ b/resources/views/components/secure-input.blade.php @@ -5,14 +5,15 @@ 'hasError', 'oldValue', 'placeholder', + 'removeName', ])
-
+ +@once + +@endonce diff --git a/resources/views/store/partials/voucher_collectorsSP.blade.php b/resources/views/store/partials/voucher_collectorsSP.blade.php index cc89908ed..f778ef0d4 100644 --- a/resources/views/store/partials/voucher_collectorsSP.blade.php +++ b/resources/views/store/partials/voucher_collectorsSP.blade.php @@ -8,7 +8,7 @@ @if (isset($pri_carer)) {{-- This section should only exist in edit rather than add new record --}} - id"))invalid @endif" type="text" @@ -21,6 +21,12 @@ class="@if($errors->has("pri_carer.$pri_carer->id"))invalid @endif" 'store.partials.errors', ['error_array' => ['This field is required']] ) + +

['This field is required']] ) + +

$hasError]) + autocomplete="off" + autocorrect="off" + spellcheck="false" + value="{{ $displayValue }}" + @if ($placeholder) placeholder="{{ $placeholder }}" @endif + @if ($filter) data-input-filter="{{ $filter }}" @endif + />
+ + @includeWhen( + $hasError, + 'store.partials.errors', + array_filter([ + 'error_array' => $errorMessages, + 'id' => $alertId, + ]) + ) +
+ +{{-- + ── Filter JS ──────────────────────────────────────────────────────────── + Loaded once per page regardless of how many x-general-input components are + rendered. Uses a data attribute rather than inline onkeyup so that: + • No JS is injected into HTML attributes. + • All filter logic lives in one place (add new filters here). + • Mirrors the @once pattern used in secure-input.blade.php. + + Supported filter names → regex applied on every 'input' event: + alpha-space strips anything that is not a lowercase letter or space + (replicates: this.value.replace(/[^a-z ]/, '') ) +--}} +@once + +@endonce diff --git a/resources/views/components/general-select.blade.php b/resources/views/components/general-select.blade.php new file mode 100644 index 000000000..b9a0af418 --- /dev/null +++ b/resources/views/components/general-select.blade.php @@ -0,0 +1,74 @@ +{{-- + general-select.blade.php + ════════════════════════════════════════════════════════════════════════ + Renders a labelled $hasError]) + > + + + @foreach ($options as $optionValue => $optionLabel) + {{-- Loose comparison intentional: old() returns strings; stored values may be int or string. --}} + + @endforeach + + + @if ($warning && $warningMessage) +
{{ $warningMessage }}
+ @endif + + @includeWhen( + $hasError, + 'store.partials.errors', + array_filter([ + 'error_array' => $errorMessages, + 'id' => $alertId, + ]) + ) + diff --git a/resources/views/store/index_registration.blade.php b/resources/views/store/index_registration.blade.php index 02199c1b6..5595e3984 100644 --- a/resources/views/store/index_registration.blade.php +++ b/resources/views/store/index_registration.blade.php @@ -69,8 +69,7 @@ class="fuzzy-text-on" @foreach ($registrations as $registration) @if ($registration->family) - @php(\App\Http\Controllers\Store\FamilyController::status($registration)) - @if( $registration->family->status === true) + @if( $registration->family->status() === true) @else @@ -83,7 +82,7 @@ class="fuzzy-text-on" {{ $registration->family->rvid }} - @if( $registration->family->status === true) + @if( $registration->family->status() === true) - @includeWhen($errors->has('consent'), 'store.partials.errors', - ['error_array' => ['Registration form must be signed in order to complete registration'], - 'id' => 'registration-alert']) - + @includeWhen( + $errors->has('consent'), + 'store.partials.errors', + ['error_array' => ['Registration form must be signed in order to complete registration'], 'id' => 'registration-alert'] + ) + @endif + {{-- This section should only exist in edit rather than add new --}} @if (isset($registration)) - + - $registration ]) }}" class="link"> + - @php(\App\Http\Controllers\Store\FamilyController::status($registration)) - @if ($registration->family->status === true ) - - - diff --git a/resources/views/store/partials/other_infoSP.blade.php b/resources/views/store/partials/other_infoSP.blade.php index ee45d645c..402acdde8 100644 --- a/resources/views/store/partials/other_infoSP.blade.php +++ b/resources/views/store/partials/other_infoSP.blade.php @@ -11,7 +11,7 @@ class="styled-checkbox @if($errors->has('consent')) invalid @endif" id="privacy-statement" name="consent" - @checked(old('consent')) + @checked(old('consent')) >

@@ -41,8 +41,7 @@ class="styled-checkbox @if($errors->has('consent')) invalid @endif" Go to voucher manager - @php(\App\Http\Controllers\Store\FamilyController::status($registration)) - @if ($registration->family->status === true ) + @if ($registration->family->status() === true ) diff --git a/resources/views/store/partials/pri-carer_fields.blade.php b/resources/views/store/partials/pri-carer_fields.blade.php new file mode 100644 index 000000000..1468423cb --- /dev/null +++ b/resources/views/store/partials/pri-carer_fields.blade.php @@ -0,0 +1,65 @@ +{{-- + partial: store.partials.primary-carer-fields + ════════════════════════════════════════════════════════════════════════ + Renders the primary carer / participant input group: + • name (text, required) + • telephone number (secure) + • email address (secure) + • ethnic background (select, optional) + • language (text, optional, alpha-space filter) + + Variables inherited from parent scope via @include: + $pri_carer – model instance when editing, undefined when creating. + Accessed throughout via ($pri_carer ?? null) so that + undefined and null are handled identically. + + Variables passed explicitly by the caller: + $labelName – label for the name field + $labelTelno – label for the telephone field + $labelEmail – label for the email field + $labelEthnicity – label for the ethnicity select + $labelLanguage – label for the language field +--}} + +
+ + + + + +
+ + + +
+ + + @if (isset($pri_carer) && empty($pri_carer->language)) + Please complete main language.
+ @endif + +
+
diff --git a/resources/views/store/partials/sec-carers_fields.blade.php b/resources/views/store/partials/sec-carers_fields.blade.php new file mode 100644 index 000000000..5feae9f4d --- /dev/null +++ b/resources/views/store/partials/sec-carers_fields.blade.php @@ -0,0 +1,82 @@ +{{-- + partial: store.partials.secondary-carers + ════════════════════════════════════════════════════════════════════════ + Renders the voucher-collector adder widget and the secondary carers + table, including both the persisted ($sec_carers) and the old()-repopulated + (new_carers) rows. + + Variables inherited from parent scope via @include: + $sec_carers – collection of persisted secondary carer models, + present on edit pages, undefined on create pages. + + Variables passed explicitly by the caller: + $newCarersErrorMessage – validation error string shown beneath the + table when new_carers.* fails (the only + text that differs between callers). +--}} + +
+ +
+ + +
+ +
+
+

You have added:

+ + + @if (isset($sec_carers)) + @foreach ($sec_carers as $sec_carer) + + + + + @endforeach + @endif + + @if (is_array(old('new_carers')) || !empty(old('new_carers'))) + @foreach (old('new_carers') as $index => $old_new_carer) + + + + + @endforeach + @endif +
+ + + +
+ + + +
+ + @includeWhen( + $errors->has('new_carers.*'), + 'store.partials.errors', + ['error_array' => [$newCarersErrorMessage]] + ) +
diff --git a/resources/views/store/partials/voucher_collectors.blade.php b/resources/views/store/partials/voucher_collectors.blade.php index d5e410f70..81f557ef1 100644 --- a/resources/views/store/partials/voucher_collectors.blade.php +++ b/resources/views/store/partials/voucher_collectors.blade.php @@ -1,173 +1,18 @@
logo - +

Voucher collectors

-
- - @if (isset($pri_carer)) - {{-- This section should only exist in edit rather than add new record --}} - id")) invalid @endif" - type="text" - value="{{ $pri_carer->name }}" - autocomplete="off" - autocorrect="off" - spellcheck="false" - >
- @includeWhen( - $errors->has("pri_carer.$pri_carer->id"), - 'store.partials.errors', - ['error_array' => ['This field is required'], 'id' => 'carer-alert'] - ) - - -
-
- - @if(empty($pri_carer->ethnicity)) -
Please complete ethnic background.
- @endif -
-
- - @if(!isset($pri_carer->language)) -
Please complete main language.
- @endif -
- @else - {{-- If this is a new record do this instead --}} - -
- @includeWhen( - $errors->has('pri_carer'), - 'store.partials.errors', - ['error_array' => ['This field is required'], 'id' => 'carer-alert'] - ) - - -
-
-
- -
- @endif -
-
- -
- - -
- -
-
-

You have added:

- - - @if (isset($sec_carers)) - @foreach ( $sec_carers as $sec_carer ) - - - - - @endforeach - @endif - - @if(is_array(old('new_carers')) || (!empty(old('new_carers')))) - @foreach (old('new_carers') as $index => $old_new_carer ) - - - - - @endforeach - @endif -
- - - -
- - -
- - @includeWhen( - $errors->has('new_carers.*'), - 'store.partials.errors', - ['error_array' => ['Please check you have valid carer names']] - ) -
+ @include('store.partials.pri-carer_fields', [ + 'labelName' => "Main carer's full name", + 'labelTelno' => "Main carer's telephone number", + 'labelEmail' => "Main carer's email address", + 'labelEthnicity' => "Main carer's ethnic background (optional)", + 'labelLanguage' => "Carer's main language (optional)", + ]) + @include('store.partials.sec-carers_fields', [ + 'newCarersErrorMessage' => 'Please check you have valid carer names', + ])
+ diff --git a/resources/views/store/partials/voucher_collectorsSP.blade.php b/resources/views/store/partials/voucher_collectorsSP.blade.php index f778ef0d4..a141cb703 100644 --- a/resources/views/store/partials/voucher_collectorsSP.blade.php +++ b/resources/views/store/partials/voucher_collectorsSP.blade.php @@ -1,107 +1,16 @@
logo - +

Voucher collectors

-
- - @if (isset($pri_carer)) - {{-- This section should only exist in edit rather than add new record --}} - id"))invalid @endif" - type="text" - value="{{ $pri_carer->name }}" - autocomplete="off" - autocorrect="off" - spellcheck="false" - >
- @includeWhen($errors->has("pri_carer.$pri_carer->id"), - 'store.partials.errors', - ['error_array' => ['This field is required']] - ) - - -
-
- - @if(empty($pri_carer->ethnicity)) -
Please complete ethnic background.
- @endif -
-
- - @if(!isset($pri_carer->language)) -
Please complete main language.
- @endif -
- @else - {{-- If this is a new record do this instead --}} -
- @includeWhen($errors->has("pri_carer"), - 'store.partials.errors', - ['error_array' => ['This field is required']] - ) - - -
-
-

-
- - @endif -
+ @include('store.partials.pri_carer-fields', [ + 'labelName' => "Main Participant's full name", + 'labelTelno' => "Main participant's telephone number", + 'labelEmail' => "Main participant's email address", + 'labelEthnicity' => "Main participant's ethnic background (optional)", + 'labelLanguage' => "Main participant's preferred language (optional)", + ])
@include('store.partials.ageInput') @@ -115,74 +24,9 @@ class="@if($errors->has('pri_carer_language')) invalid @endif"
-
- -
- - -
- - -
-
-

You have added:

- - - @if (isset($sec_carers)) - @foreach ( $sec_carers as $sec_carer ) - - - - - @endforeach - @endif - - @if(is_array(old('new_carers')) || (!empty(old('new_carers')))) - @foreach (old('new_carers') as $index => $old_new_carer ) - - - - - @endforeach - @endif -
- - - -
- - -
- - @includeWhen($errors->has('new_carers.*'), - 'store.partials.errors', - ['error_array' => ['Please check you have valid collector names']] - ) -
+ @include('store.partials.sec-carers_feilds', [ + 'newCarersErrorMessage' => 'Please check you have valid collector names', + ])
@pushonce('bottom:vouchercollectorsSP') From 7fb0d65e8422dc335fc61f764a1f113decb31166 Mon Sep 17 00:00:00 2001 From: charles strange Date: Sun, 29 Mar 2026 23:09:51 +0100 Subject: [PATCH 029/168] fix: existing tests need new input targets --- resources/views/store/partials/sec-carers_fields.blade.php | 2 +- resources/views/store/partials/voucher_collectorsSP.blade.php | 4 ++-- tests/Feature/Store/EditPageTest.php | 2 +- tests/Feature/Store/RegistrationPageTest.php | 2 +- tests/Unit/Routes/StoreRoutesTest.php | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/views/store/partials/sec-carers_fields.blade.php b/resources/views/store/partials/sec-carers_fields.blade.php index 5feae9f4d..2a779c7e6 100644 --- a/resources/views/store/partials/sec-carers_fields.blade.php +++ b/resources/views/store/partials/sec-carers_fields.blade.php @@ -1,5 +1,5 @@ {{-- - partial: store.partials.secondary-carers + partial: store.partials.sec-carers_fields ════════════════════════════════════════════════════════════════════════ Renders the voucher-collector adder widget and the secondary carers table, including both the persisted ($sec_carers) and the old()-repopulated diff --git a/resources/views/store/partials/voucher_collectorsSP.blade.php b/resources/views/store/partials/voucher_collectorsSP.blade.php index a141cb703..e50a54335 100644 --- a/resources/views/store/partials/voucher_collectorsSP.blade.php +++ b/resources/views/store/partials/voucher_collectorsSP.blade.php @@ -4,7 +4,7 @@

Voucher collectors

- @include('store.partials.pri_carer-fields', [ + @include('store.partials.pri-carer_fields', [ 'labelName' => "Main Participant's full name", 'labelTelno' => "Main participant's telephone number", 'labelEmail' => "Main participant's email address", @@ -24,7 +24,7 @@ - @include('store.partials.sec-carers_feilds', [ + @include('store.partials.sec-carers_fields', [ 'newCarersErrorMessage' => 'Please check you have valid collector names', ]) diff --git a/tests/Feature/Store/EditPageTest.php b/tests/Feature/Store/EditPageTest.php index aada00304..629a6702e 100644 --- a/tests/Feature/Store/EditPageTest.php +++ b/tests/Feature/Store/EditPageTest.php @@ -120,7 +120,7 @@ public function testItShowsAPrimaryCarerInput(): void $pri_carer = $this->registration->family->carers->first(); $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.edit', [ 'registration' => $this->registration ])) - ->seeElement('input[id="carer"][value="' . $pri_carer->name . '"]') + ->seeElement('input[id="pri_carer"][value="' . $pri_carer->name . '"]') ; } diff --git a/tests/Feature/Store/RegistrationPageTest.php b/tests/Feature/Store/RegistrationPageTest.php index 6a70033c6..81eb35941 100644 --- a/tests/Feature/Store/RegistrationPageTest.php +++ b/tests/Feature/Store/RegistrationPageTest.php @@ -117,7 +117,7 @@ public function testItShowsAFormSaveButton(): void { $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) - ->seeInElement('button[type=Submit]', 'Save Family') + ->seeInElement('button[type=submit]', 'Save Family') ; } diff --git a/tests/Unit/Routes/StoreRoutesTest.php b/tests/Unit/Routes/StoreRoutesTest.php index dcdeef642..753a5d5b5 100644 --- a/tests/Unit/Routes/StoreRoutesTest.php +++ b/tests/Unit/Routes/StoreRoutesTest.php @@ -208,7 +208,7 @@ public function testUpdateRouteGate(): void ->press('Save Changes') ->seePageIs($edit_route) ->assertResponseStatus(200) - ->seeElement('input[id=carer][value=changedByTest]'); + ->seeElement('input[id=pri_carer][value=changedByTest]'); Auth::logout(); From bbd78af1d69837012477d800c2a26c687186b393 Mon Sep 17 00:00:00 2001 From: charles strange Date: Mon, 30 Mar 2026 00:42:37 +0100 Subject: [PATCH 030/168] fix: remove redundant parts --- app/View/Components/GeneralSelect.php | 6 - app/View/Components/PasswordInput.php | 100 ++++++++++++ app/View/Components/SecureInput.php | 40 ----- .../views/components/general-input.blade.php | 44 +---- .../views/components/general-select.blade.php | 7 - .../partials/input-filter.blade.php | 36 +++++ .../views/components/password-input.blade.php | 150 ++++++++++++++++++ .../views/components/secure-input.blade.php | 99 ------------ .../store/partials/pri-carer_fields.blade.php | 60 ++++--- 9 files changed, 328 insertions(+), 214 deletions(-) create mode 100644 app/View/Components/PasswordInput.php delete mode 100644 app/View/Components/SecureInput.php create mode 100644 resources/views/components/partials/input-filter.blade.php create mode 100644 resources/views/components/password-input.blade.php delete mode 100644 resources/views/components/secure-input.blade.php diff --git a/app/View/Components/GeneralSelect.php b/app/View/Components/GeneralSelect.php index afed58e49..5368a3cad 100644 --- a/app/View/Components/GeneralSelect.php +++ b/app/View/Components/GeneralSelect.php @@ -33,10 +33,6 @@ class GeneralSelect extends Component * @param string|null $alertId Optional id attribute forwarded to the errors partial. * @param string $placeholder Label for the empty/default option. Default: 'Please select'. * @param int|string $placeholderValue Value attribute of the placeholder option. Default: '0'. - * @param bool $warning When true, renders a warning below the select. - * The caller evaluates the condition and passes the result, - * keeping field-specific logic out of the component. - * @param string|null $warningMessage Text displayed inside the tag when $warning is true. */ public function __construct( public readonly string $name, @@ -50,8 +46,6 @@ public function __construct( public readonly ?string $alertId = null, public readonly string $placeholder = 'Please select', public readonly int|string $placeholderValue = '0', - public readonly bool $warning = false, - public readonly ?string $warningMessage = null, ) { } diff --git a/app/View/Components/PasswordInput.php b/app/View/Components/PasswordInput.php new file mode 100644 index 000000000..f46bff027 --- /dev/null +++ b/app/View/Components/PasswordInput.php @@ -0,0 +1,100 @@ +get(). + * @param string|null $alertId Forwarded as id= to the errors partial. + * @param string|null $filter Named JS sanitiser (e.g. 'alpha-space'). + * Applied via data-input-filter on the 'input' event. + * @param string|null $placeholder Placeholder shown in virgin state only. + * The phantom-prefill placeholder ('••••••••') is + * always used in prefilled state regardless of this value. + */ + public function __construct( + public readonly string $name, + public readonly string $label, + public readonly int|string|null $modelId = null, + public readonly bool $existingPassword = false, + public readonly ?string $errorKey = null, + public readonly ?string $errorMessage = null, + public readonly ?string $alertId = null, + public readonly ?string $filter = null, + public readonly ?string $placeholder = null, + ) { + } + + public function render(): View + { + /** @var ViewErrorBag $errors */ + $errors = session('errors', new ViewErrorBag()); + + // ── Name & old() key ────────────────────────────────────────────── + $inputName = $this->modelId !== null + ? "{$this->name}[{$this->modelId}]" + : $this->name; + + $oldKey = $this->modelId !== null + ? "{$this->name}.{$this->modelId}" + : $this->name; + + // ── Error key ───────────────────────────────────────────────────── + $resolvedErrorKey = $this->errorKey ?? $oldKey; + + // ── Phantom-prefill state ───────────────────────────────────────── + // Active only when editing a record with an existing password AND + // the user hasn't re-entered anything (no old() value from a failed + // submission). A failed submission must always show the old() value + // unmasked so the user can correct it. + $oldValue = old($oldKey); + $isPrefilled = $this->existingPassword && $oldValue === null; + + // ── Display value ───────────────────────────────────────────────── + // Phantom state: field is visually empty (bullets come from placeholder). + // Virgin state: old() value on failed submit, otherwise empty. + $displayValue = $isPrefilled ? null : $oldValue; + + // ── Errors ──────────────────────────────────────────────────────── + $hasError = $errors->has($resolvedErrorKey); + $errorMessages = $this->errorMessage !== null + ? [$this->errorMessage] + : $errors->get($resolvedErrorKey); + + return view('components.password-input', [ + 'inputName' => $inputName, + 'hasError' => $hasError, + 'displayValue' => $displayValue, + 'errorMessages' => $errorMessages, + 'isPrefilled' => $isPrefilled, + ]); + } +} diff --git a/app/View/Components/SecureInput.php b/app/View/Components/SecureInput.php deleted file mode 100644 index 333c71275..000000000 --- a/app/View/Components/SecureInput.php +++ /dev/null @@ -1,40 +0,0 @@ - $errors->has($this->name), - 'oldValue' => old($this->name), - 'placeholder' => $this->placeholder ?? ( - $this->filled - ? str_repeat('•', 16) - : 'Enter ' . strtolower($this->label) . '…' - ), - ]); - } -} diff --git a/resources/views/components/general-input.blade.php b/resources/views/components/general-input.blade.php index 701050912..11cc4cc70 100644 --- a/resources/views/components/general-input.blade.php +++ b/resources/views/components/general-input.blade.php @@ -21,10 +21,10 @@ @props([ 'name', 'label', - 'type' => 'text', - 'filter' => null, + 'type' => 'text', + 'filter' => null, 'placeholder' => null, - 'alertId' => null, + 'alertId' => null, // Injected by the component class: 'inputName', 'hasError', @@ -58,36 +58,8 @@ ) -{{-- - ── Filter JS ──────────────────────────────────────────────────────────── - Loaded once per page regardless of how many x-general-input components are - rendered. Uses a data attribute rather than inline onkeyup so that: - • No JS is injected into HTML attributes. - • All filter logic lives in one place (add new filters here). - • Mirrors the @once pattern used in secure-input.blade.php. - - Supported filter names → regex applied on every 'input' event: - alpha-space strips anything that is not a lowercase letter or space - (replicates: this.value.replace(/[^a-z ]/, '') ) ---}} -@once - -@endonce +{{-- Filter listener extracted to a shared partial so x-password-input can + include the same file without duplicating the script. The @once key + inside the partial guarantees one registration per page regardless of + how many components include it. --}} +@include('components.partials.input-filter') diff --git a/resources/views/components/general-select.blade.php b/resources/views/components/general-select.blade.php index b9a0af418..5876c91b3 100644 --- a/resources/views/components/general-select.blade.php +++ b/resources/views/components/general-select.blade.php @@ -29,8 +29,6 @@ 'options', 'placeholder' => 'Please select', 'placeholderValue' => '0', - 'warning' => false, - 'warningMessage' => null, 'alertId' => null, // Injected by the component class: 'inputName', @@ -58,11 +56,6 @@ >{{ $optionLabel }} @endforeach - - @if ($warning && $warningMessage) -
{{ $warningMessage }}
- @endif - @includeWhen( $hasError, 'store.partials.errors', diff --git a/resources/views/components/partials/input-filter.blade.php b/resources/views/components/partials/input-filter.blade.php new file mode 100644 index 000000000..f5744c743 --- /dev/null +++ b/resources/views/components/partials/input-filter.blade.php @@ -0,0 +1,36 @@ +{{-- + components/partials/input-filter.blade.php + ════════════════════════════════════════════════════════════════════════ + Shared once-per-page listener for the data-input-filter attribute. + + @include this partial from any component that uses data-input-filter. + The @once guard ensures the listener is registered exactly once no matter + how many components on the page include this file. + + Supported filter names → regex applied on every 'input' event: + alpha-space strips anything that is not a letter or space + replicates: value.replace(/[^a-zA-Z ]/, '') + + To add a new filter, add an entry to the FILTERS map below. +--}} +@once('input-filter-js') + +@endonce diff --git a/resources/views/components/password-input.blade.php b/resources/views/components/password-input.blade.php new file mode 100644 index 000000000..268a2c866 --- /dev/null +++ b/resources/views/components/password-input.blade.php @@ -0,0 +1,150 @@ +{{-- + password-input.blade.php + ════════════════════════════════════════════════════════════════════════ + Renders a labelled, unmasked password field with phantom-prefill support. + + The field always uses type="text" so the value is visible to the user. + Masking in edit mode is achieved through a suppressed name + bullet + placeholder rather than type="password", which avoids browser autofill + quirks and the value-loss that comes with type switching. + + ── States ─────────────────────────────────────────────────────────────── + + Phantom-prefill ($isPrefilled = true) + name="" nothing submitted; backend treats absence as "unchanged" + value="" field appears empty + placeholder="••••••••" visual cue that a password exists + data-pw-prefilled present — JS hook that triggers virgin transition + + Virgin ($isPrefilled = false) + name="{{ $inputName }}" submitted normally + value="{{ $displayValue }}" old() value or empty + placeholder="{{ $placeholder }}" + data-pw-prefilled absent + + Both states carry data-pw-name when the field is prefillable, so the + Escape handler can revert from virgin → phantom at any point. + + ── Props (declared here — consumed directly from the blade tag) ────────── + name – base field name, used for id= and label for= + label – visible label text + filter – named JS sanitiser ('alpha-space' supported) + placeholder – placeholder for virgin state only + alertId – optional id forwarded to the errors partial + + ── Props (injected by the component class via render()) ───────────────── + inputName – resolved name (may be array-style: field[123]) + hasError – bool + displayValue – string|null + errorMessages – array + isPrefilled – bool + existingPassword – bool (public prop, auto-injected from class) +--}} + +@props([ + 'name', + 'label', + 'filter' => null, + 'placeholder' => null, + 'alertId' => null, + // Injected by the component class: + 'inputName', + 'hasError', + 'displayValue', + 'errorMessages', + 'isPrefilled', + 'existingPassword' => false, +]) + +
+
+ + $hasError]) + />
+ + @includeWhen( + $hasError, + 'store.partials.errors', + array_filter([ + 'error_array' => $errorMessages, + 'id' => $alertId, + ]) + ) +
+ +{{-- Shared filter listener (safe to include from both x-general-input and + x-password-input; the @once key ensures one registration per page). --}} +@include('components.partials.input-filter') + +{{-- Phantom-prefill state machine. + Two delegated listeners on document cover every x-password-input on + the page with a single registration. --}} +@once('pw-input-js') + +@endonce diff --git a/resources/views/components/secure-input.blade.php b/resources/views/components/secure-input.blade.php deleted file mode 100644 index 90f18b9b6..000000000 --- a/resources/views/components/secure-input.blade.php +++ /dev/null @@ -1,99 +0,0 @@ -@props([ - 'name', - 'label', - 'filled' => false, - 'hasError', - 'oldValue', - 'placeholder', - 'removeName', -]) - -
- - - - merge(['class' => 'invalid']) }} @endif - /> - - @if ($filled) - {{-- - Disabled by default so it is excluded from the request. - JS enables it (and disables the password input) only when - the user has touched the field and left it empty. - Re-enabled server-side when old() signals a failed submission - where the user had already cleared the field. - --}} - - @endif - - @includeWhen( - $hasError, - 'store.partials.errors', - ['error_array' => $errors->get($name)] - ) - -
- -@once - -@endonce diff --git a/resources/views/store/partials/pri-carer_fields.blade.php b/resources/views/store/partials/pri-carer_fields.blade.php index 1468423cb..245251258 100644 --- a/resources/views/store/partials/pri-carer_fields.blade.php +++ b/resources/views/store/partials/pri-carer_fields.blade.php @@ -3,8 +3,8 @@ ════════════════════════════════════════════════════════════════════════ Renders the primary carer / participant input group: • name (text, required) - • telephone number (secure) - • email address (secure) + • telephone number (password-input — unmasked entry, phantom-prefill on edit) + • email address (password-input — unmasked entry, phantom-prefill on edit) • ethnic background (select, optional) • language (text, optional, alpha-space filter) @@ -23,43 +23,51 @@
- - -
+
+ @if (isset($pri_carer) && empty($pri_carer->ethnicity)) + Please complete ethnic background. + @endif -
+
@if (isset($pri_carer) && empty($pri_carer->language)) - Please complete main language.
+ Please complete main language. @endif -
+
From f5b695671a30121a33a4f2fe78c78ba3ed2adbd8 Mon Sep 17 00:00:00 2001 From: charles strange Date: Mon, 30 Mar 2026 01:27:28 +0100 Subject: [PATCH 031/168] fix: custom @pushonce directive conflict with L12's --- app/Providers/AppServiceProvider.php | 13 ------------- resources/views/store/partials/ageInput.blade.php | 2 +- resources/views/store/partials/dobInput.blade.php | 2 +- resources/views/store/partials/family.blade.php | 2 +- .../views/store/partials/householdSP.blade.php | 2 +- .../store/partials/voucher_collectorsSP.blade.php | 2 +- 6 files changed, 5 insertions(+), 18 deletions(-) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index bf42e88da..e8dba1264 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -31,19 +31,6 @@ public function boot(): void // Needed because we're still serialising cookies! Passport::withCookieSerialization(); - - // adds "push once" - Blade::directive('pushonce', static function ($expression) { - [$pushName, $pushSub] = explode(':', trim(substr($expression, 1, -1))); - - $key = '__pushonce_' . str_replace('-', '_', $pushName) . '_' . str_replace('-', '_', $pushSub); - - return "{$key})): \$__env->{$key} = 1; \$__env->startPush('{$pushName}'); ?>"; - }); - Blade::directive('endpushonce', static function ($expression) { - return 'stopPush(); endif; ?>'; - }); - View::composer('*', PaymentsComposer::class); } diff --git a/resources/views/store/partials/ageInput.blade.php b/resources/views/store/partials/ageInput.blade.php index 953e4f028..1ff37d5fe 100644 --- a/resources/views/store/partials/ageInput.blade.php +++ b/resources/views/store/partials/ageInput.blade.php @@ -12,7 +12,7 @@ class="age" > -@pushonce('js:ageinput') +@pushonce('js') From 5232319395345cbf683f9978c9e3760b13e67763 Mon Sep 17 00:00:00 2001 From: charles strange Date: Mon, 30 Mar 2026 11:05:39 +0100 Subject: [PATCH 032/168] refactor: store and update functions --- app/Family.php | 34 +- .../Store/RegistrationController.php | 432 ++++++++---------- 2 files changed, 214 insertions(+), 252 deletions(-) diff --git a/app/Family.php b/app/Family.php index 4c37964cb..486a61a26 100644 --- a/app/Family.php +++ b/app/Family.php @@ -9,6 +9,7 @@ use App\Traits\Evaluable; use DB; use Eloquent; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -21,10 +22,10 @@ * @property string $rejoin_on * @property string $leave_amount * @property int $centre_sequence - * @property Carer[] $carers - * @property Child[] $children - * @property Note[] $notes - * @property Registration[] $registrations + * @property Collection|Carer[] $carers + * @property Collection|Child[] $children + * @property Collection|Note[] $notes + * @property Collection|Registration[] $registrations * @property Centre $initialCentre * @property string $rvid * @@ -63,14 +64,7 @@ class Family extends Model implements IEvaluee ]; /** - * The attributes that should be hidden for arrays. - * - * @var array - */ - protected $hidden = []; - - /** - * Attributes to autocalculate and add when we ask. + * Attributes to auto-calculate and add when we ask. * * @var array */ @@ -130,11 +124,9 @@ public function getEvaluator(): AbstractEvaluator } /** - * Gets the due date or Null; - * - * @return mixed|null + * Gets the due date or Null */ - public function getExpectingAttribute() + public function getExpectingAttribute(): mixed { $due = null; foreach ($this->children as $child) { @@ -190,7 +182,7 @@ public function getRvidAttribute(): string */ public function carers(): HasMany { - return $this->hasMany('App\Carer'); + return $this->hasMany(Carer::class); } /** @@ -199,7 +191,7 @@ public function carers(): HasMany */ public function children(): HasMany { - return $this->hasMany('App\Child'); + return $this->hasMany(Child::class); } /** @@ -209,7 +201,7 @@ public function children(): HasMany */ public function notes(): HasMany { - return $this->hasMany('App\Note'); + return $this->hasMany(Note::class); } /** @@ -219,7 +211,7 @@ public function notes(): HasMany */ public function registrations(): HasMany { - return $this->hasMany('App\Registration'); + return $this->hasMany(Registration::class); } /** @@ -228,7 +220,7 @@ public function registrations(): HasMany */ public function initialCentre(): BelongsTo { - return $this->belongsTo('App\Centre', 'initial_centre_id'); + return $this->belongsTo(Centre::class, 'initial_centre_id'); } public function scopeWithPrimaryCarer($query) diff --git a/app/Http/Controllers/Store/RegistrationController.php b/app/Http/Controllers/Store/RegistrationController.php index 75be64415..ff02d58ec 100644 --- a/app/Http/Controllers/Store/RegistrationController.php +++ b/app/Http/Controllers/Store/RegistrationController.php @@ -16,7 +16,6 @@ use Auth; use Carbon\Carbon; use DB; -use Exception; use HighSolutions\LaravelSearchy\Facades\Searchy; use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\View; @@ -143,6 +142,58 @@ public function index(Request $request): View|Factory|Application return view('store.index_registration', $data); } + private function fuzzySearch($family_name, $pri_carers): array + { + // Get the current database driver + $connection = config('database.default'); + $driver = config("database.connections.$connection.driver"); + + if ($driver === 'mysql') { + // We can use Searchy for mysql; defaults to "fuzzy" search; + // results are a collection of basic objects, but we can still "pluck()" + $filtered_family_ids = Searchy::search('carers') + ->fields('name') + ->query($family_name) + ->getQuery() + ->whereIn('id', $pri_carers) + ->pluck('family_id') + ->toArray(); + } else { + // We may not be able to use Searchy, so we default to unfuzzy. + $filtered_family_ids = $this->exactSearch($family_name, $pri_carers); + } + + return $filtered_family_ids; + } + + private function exactSearch($family_name, $pri_carers): array + { + $carers = Carer::query() + ->where('name', 'LIKE', "%$family_name%") + ->whereIn('id', $pri_carers) + ->get(); + + $startsWithExact = []; + $wholeWord = []; + $theRest = []; + + foreach ($carers as $carer) { + $names = array_map('strtolower', explode(" ", $carer->name)); + + if (count($names) !== 0) { + if (strtolower($names[0]) === strtolower($family_name)) { + $startsWithExact[] = $carer->family_id; + } elseif (in_array($family_name, $names)) { + $wholeWord[] = $carer->family_id; + } else { + $theRest[] = $carer->family_id; + } + } + } + + return array_merge($startsWithExact, $wholeWord, $theRest); + } + /** * Returns the registration page * @@ -171,11 +222,56 @@ public function create(): View|Factory|Application "sponsorsRequiresID" => $sponsorsRequiresID, "programme" => $user->centre->sponsor->programme, 'leaver' => false, - 'children' => $children ?? [] + 'children' => $children ?? [], ]; return view('store.create_registration', $data); } + /** + * Makes children from input data + * @param array $children + * @return array + */ + private function makeChildrenFromInput(array $children = []): array + { + return Arr::map( + $children, + static function ($child): Child { + // Note: Carbon uses different time formats than laravel validation + // For crazy reasons known only to the creators of Carbon, when no day provided, + // createFromFormat - defaults to 31 - which bumps to next month if not a real day. + // So we want '2013-02-01' not '2013-02-31'... + $month_of_birth = Carbon::createFromFormat('Y-m-d', $child['dob'] . '-01'); + + // Check and set verified, or null + $verified = null; + if (array_key_exists('verified', $child)) { + $verified = (bool)$child['verified']; + } + + // Check and set deferred, or null + $deferred = 0; + if (array_key_exists('deferred', $child)) { + $deferred = (bool)$child['deferred']; + } + + // Check and set is_pri_carer, or null + $is_pri_carer = null; + if (array_key_exists('is_pri_carer', $child)) { + $is_pri_carer = (bool)$child['is_pri_carer']; + } + + return new Child([ + 'born' => $month_of_birth->isPast(), + 'dob' => $month_of_birth->toDateTimeString(), + 'verified' => $verified, + 'deferred' => $deferred, + 'is_pri_carer' => $is_pri_carer, + ]); + } + ); + } + /** * Show the Registration / Family edit form * @@ -190,7 +286,7 @@ public function edit(Registration $registration): View|Factory|Application $data = [ 'user_name' => $user->name, 'centre_name' => $user->centre?->name, - 'programme' => $user->centre?->sponsor->programme + 'programme' => $user->centre?->sponsor->programme, ]; // Get the registration, with deep eager-loaded Family (with Children and Carers) @@ -388,308 +484,182 @@ public function printBatchIndividualFamilyForms() /** * Stores an incoming Registration. - * - * @param StoreNewRegistrationRequest $request - * @return RedirectResponse - * @throws Throwable $e */ public function store(StoreNewRegistrationRequest $request): RedirectResponse { - // Create Carers, merge primary carer - $carers = array_map( - static function ($carer) use ($request) { + $data = $request->validated(); + + $carers = array_merge( + array_map(static function (string $name) use ($data): Carer { return new Carer([ - 'name' => $carer, - 'ethnicity' => $request->get("pri_carer_ethnicity"), - 'language' => $request->get("pri_carer_language") + 'name' => $name, + 'ethnicity' => $data['pri_carer_ethnicity'] ?? null, + 'language' => $data['pri_carer_language'] ?? null, + 'telnosecret' => $data['pri_carer_telno'] ?? null, + 'emailsecret' => $data['pri_carer_email'] ?? null ]); - }, - array_merge( - (array)$request->get('pri_carer'), - (array)$request->get('new_carers') + }, (array)($data['pri_carer'] ?? [])), + array_map( + static function (string $name): Carer { + return new Carer(['name' => $name]); + }, + (array)($data['new_carers'] ?? []) ) ); - // Create Children - $children = $this->makeChildrenFromInput( - (array)$request->get('children') - ); + $children = $this->makeChildrenFromInput((array)($data['children'] ?? [])); - // Create Registration $registration = new Registration([ 'consented_on' => Carbon::now(), - 'eligibility_hsbs' => $request->get('eligibility-hsbs'), - 'eligibility_nrpf' => $request->get('eligibility-nrpf'), - 'eligible_from' => ($request->get('eligibility-hsbs') === 'healthy-start-receiving') - ? Carbon::now() - : null, + 'eligibility_hsbs' => $data['eligibility-hsbs'], + 'eligibility_nrpf' => $data['eligibility-nrpf'], + 'eligible_from' => $data['eligibility-hsbs'] === 'healthy-start-receiving' ? Carbon::now() : null, ]); - // Duplicate families are fine at this point. $family = new Family(); - - // Set the RVID using the User's Centre. $family->lockToCentre(Auth::user()->centre); - // Try to transact, so we can roll it back try { - DB::transaction(static function () use ($registration, $family, $carers, $children) { + DB::transaction(callback: static function () use ($registration, $family, $carers, $children): void { $family->save(); $family->carers()->saveMany($carers); $family->children()->saveMany($children); $registration->family()->associate($family); + // TODO - BUGWATCH! this will default to users, default centre if the selector is set to "all" $registration->centre()->associate(Auth::user()->centre); $registration->save(); }); - } catch (Exception $e) { - // Oops! Log that - Log::error('Bad transaction for ' . __CLASS__ . '@' . __METHOD__ . ' by service user ' . Auth::id()); + } catch (Throwable $e) { + Log::error(sprintf('Bad transaction for %s@%s by service user %s', __CLASS__, __METHOD__, Auth::id())); Log::error($e->getTraceAsString()); - // Throw it back to the user + return redirect()->route('store.registration.create')->withErrors('Registration failed.'); } - // Or return the success - Log::info('Registration ' . $registration->id . ' created by service user ' . Auth::id()); - // and go to the edit page for the new registration - return redirect() - ->route('store.registration.edit', ['registration' => $registration->id]) - ->with('message', 'Registration created.'); - } - - /** - * Makes children from input data - * @param array $children - * @return array - */ - private function makeChildrenFromInput(array $children = []): array - { - return Arr::map( - $children, - static function ($child) { - // Note: Carbon uses different time formats than laravel validation - // For crazy reasons known only to the creators of Carbon, when no day provided, - // createFromFormat - defaults to 31 - which bumps to next month if not a real day. - // So we want '2013-02-01' not '2013-02-31'... - $month_of_birth = Carbon::createFromFormat('Y-m-d', $child['dob'] . '-01'); - // Check and set verified, or null - $verified = null; - if (array_key_exists('verified', $child)) { - $verified = (bool)$child['verified']; - } + Log::info(sprintf('Registration %s created by service user %s', $registration->id, Auth::id())); - // Check and set deferred, or null - $deferred = 0; - if (array_key_exists('deferred', $child)) { - $deferred = (bool)$child['deferred']; - } - - // Check and set is_pri_carer, or null - $is_pri_carer = null; - if (array_key_exists('is_pri_carer', $child)) { - $is_pri_carer = (bool)$child['is_pri_carer']; - } - - return new Child([ - 'born' => $month_of_birth->isPast(), - 'dob' => $month_of_birth->toDateTimeString(), - 'verified' => $verified, - 'deferred' => $deferred, - 'is_pri_carer' => $is_pri_carer, - ]); - } - ); + return redirect() + ->route('store.registration.edit', $registration) + ->with('message', 'Registration created.'); } /** * Update a Registration - * - * @param StoreUpdateRegistrationRequest $request - * @param Registration $registration - * @return RedirectResponse */ public function update(StoreUpdateRegistrationRequest $request, Registration $registration): RedirectResponse { + $data = $request->validated(); $amendedCarers = []; - // Fetch eligibility - $eligibility_hsbs = $request->get('eligibility-hsbs'); - $eligibility_nrpf = $request->get('eligibility-nrpf'); - $deferred = $request->get('deferred'); + // Primary carer + $priCarerInput = (array)($data['pri_carer'] ?? []); + $priCarerId = (int)array_key_first($priCarerInput); + $priCarer = Carer::findOrFail($priCarerId); - //Prevent the date changing if you're just editing a different field - $eligible_from = ($eligibility_hsbs === 'healthy-start-receiving' && !$registration->eligible_from) - ? Carbon::now() - : null; - - // NOTE: Following refactor where we needed to retain Carer ids. - // Possible that we might want to add flag to carer to distinguish Main from Secondary, - // to simplify method below for sorting and updating carer entries. - - // Update primary carer. - $carerInput = (array)$request->get("pri_carer"); - $carerEthnicity = $request->get("pri_carer_ethnicity"); - $carerLanguage = $request->get('pri_carer_language'); - $carerKey = key($carerInput); - $carer = Carer::find($carerKey); - - if ($carer->name !== $carerInput[$carer->id]) { - $carer->name = $carerInput[$carer->id]; - $amendedCarers[] = $carer; + if ($priCarer->name !== $priCarerInput[$priCarerId]) { + $priCarer->name = $priCarerInput[$priCarerId]; + $amendedCarers[] = $priCarer; } - if ($carerEthnicity !== null && $carerEthnicity !== $carerEthnicity[$carer->id]) { - $carer->ethnicity = $carerEthnicity[$carer->id]; - $amendedCarers[] = $carer; + + $priEthnicity = $data['pri_carer_ethnicity'][$priCarerId] ?? null; + if ($priEthnicity !== null && $priCarer->ethnicity !== $priEthnicity) { + $priCarer->ethnicity = $priEthnicity; + $amendedCarers[] = $priCarer; } - if ($carerLanguage !== null && $carerLanguage !== $carerLanguage[$carer->id]) { - $carer->language = $carerLanguage[$carer->id]; - $amendedCarers[] = $carer; + + $priLanguage = $data['pri_carer_language'][$priCarerId] ?? null; + if ($priLanguage !== null && $priCarer->language !== $priLanguage) { + $priCarer->language = $priLanguage; + $amendedCarers[] = $priCarer; } - // Find secondary carers id's in the DB - $carersInput = (array)$request->get("sec_carers"); - $carersKeys = $registration->family->carers->pluck("id")->toArray(); - // remove carerKey from that; - if (($key = array_search($carerKey, $carersKeys)) !== false) { - unset($carersKeys[$key]); + // emailsecure and telnosecure are special + $priEmail = $data['pri_carer_email'][$priCarerId] ?? null; + if ($priEmail !== null && $priCarer->language !== $priEmail) { + $priCarer->emailsecure = $priEmail; + $amendedCarers[] = $priCarer; } - // Those in the DB, not in the input can be scheduled for deletion; - $carersInputKeys = array_keys($carersInput); - $carersKeysToDelete = array_diff($carersKeys, $carersInputKeys); + $priTelno = $data['pri_carer_email'][$priCarerId] ?? null; + if ($priTelno !== null && $priCarer->language !== $priTelno) { + $priCarer->telnosecure = $priTelno; + $amendedCarers[] = $priCarer; + } - // Get the secondary carers. - $carers = Carer::whereIn("id", $carersInputKeys)->get(); + // Secondary carers — diff DB state against input to find stale IDs + $secCarersInput = (array)($data['sec_carers'] ?? []); + $staleCarerIds = $registration->family->carers + ->pluck('id') + ->reject(function (int $id) use ($priCarerId): bool { + return $id === $priCarerId; + }) + ->diff(array_keys($secCarersInput)) + ->values() + ->all(); - // roll though those and amend them if necessary. - foreach ($carers as $carer) { - if ($carer->name !== $carersInput[$carer->id]) { - $carer->name = $carersInput[$carer->id]; + foreach (Carer::whereIn('id', array_keys($secCarersInput))->get() as $carer) { + if ($carer->name !== $secCarersInput[$carer->id]) { + $carer->name = $secCarersInput[$carer->id]; $amendedCarers[] = $carer; } } - // Create new carers + // New carers and children $newCarers = array_map( - static function ($new_carer) { - return new Carer(['name' => $new_carer]); + static function (string $name): Carer { + return new Carer(['name' => $name]); }, - (array)$request->get('new_carers') + (array)($data['new_carers'] ?? []) ); - // Create New Children - $children = $this->makeChildrenFromInput( - (array)$request->get('children') - ); + $children = $this->makeChildrenFromInput((array)($data['children'] ?? [])); - $family = $registration->family; + // Preserve eligible_from if already set; only stamp it on first transition + $eligibleFrom = ($data['eligibility-hsbs'] === 'healthy-start-receiving' && !$registration->eligible_from) + ? Carbon::now() + : $registration->eligible_from; - // Try to transact, so we can roll it back try { DB::transaction(static function () use ( $registration, - $family, $amendedCarers, $newCarers, - $carersKeysToDelete, + $staleCarerIds, $children, - $eligibility_hsbs, - $eligibility_nrpf, - $eligible_from - ) { - - // delete the missing carers - Carer::whereIn('id', $carersKeysToDelete)->get()->each(function ($carer) { - $carer->delete(); - }); + $data, + $eligibleFrom, + ): void { + $family = $registration->family; - // delete the children. still messy. - $family->children()->delete(); - - // save the new ones! + Carer::whereIn('id', $staleCarerIds)->delete(); $family->carers()->saveMany($newCarers); - $family->children()->saveMany($children); - // save changes to the changed names - collect($amendedCarers)->each( - function (Carer $model) { - $model->save(); - } - ); + $family->children()->delete(); + $family->children()->saveMany($children); - // update eligibility - $registration->eligibility_hsbs = $eligibility_hsbs; - $registration->eligibility_nrpf = $eligibility_nrpf; - $registration->eligible_from = $eligible_from; + collect($amendedCarers)->unique()->each(function (Carer $carer) { + return $carer->save(); + }); - // save changes to registration. - $registration->save(); + $registration->fill([ + 'eligibility_hsbs' => $data['eligibility-hsbs'], + 'eligibility_nrpf' => $data['eligibility-nrpf'], + 'eligible_from' => $eligibleFrom + ])->save(); }); } catch (Throwable $e) { - // Oops! Log that - Log::error('Bad transaction for ' . __CLASS__ . '@' . __METHOD__ . ' by service user ' . Auth::id()); + Log::error(sprintf('Bad transaction for %s@%s by service user %s', __CLASS__, __METHOD__, Auth::id())); Log::error($e->getTraceAsString()); - // Throw it back to the user - return redirect()->route('store.registration.edit', ['registration' => $registration->id]) - ->withErrors('Registration update failed.'); - } - // Or return the success - Log::info('Registration ' . $registration->id . ' updated by service user ' . Auth::id()); - // and go back to edit page for the changed registration - return redirect() - ->route('store.registration.edit', ['registration' => $registration->id]) - ->with('message', 'Registration updated.'); - } - - private function exactSearch($family_name, $pri_carers): array - { - $carers = Carer::query() - ->where('name', 'LIKE', "%$family_name%") - ->whereIn('id', $pri_carers) - ->get(); - - $startsWithExact = []; - $wholeWord = []; - $theRest = []; - foreach ($carers as $carer) { - $names = array_map('strtolower', explode(" ", $carer->name)); - - if (count($names) !== 0) { - if (strtolower($names[0]) === strtolower($family_name)) { - $startsWithExact[] = $carer->family_id; - } elseif (in_array($family_name, $names)) { - $wholeWord[] = $carer->family_id; - } else { - $theRest[] = $carer->family_id; - } - } + return redirect() + ->route('store.registration.edit', $registration) + ->withErrors('Registration update failed.'); } - return array_merge($startsWithExact, $wholeWord, $theRest); - } - - private function fuzzySearch($family_name, $pri_carers): array - { - // Get the current database driver - $connection = config('database.default'); - $driver = config("database.connections.$connection.driver"); - - if ($driver === 'mysql') { - // We can use Searchy for mysql; defaults to "fuzzy" search; - // results are a collection of basic objects, but we can still "pluck()" - $filtered_family_ids = Searchy::search('carers') - ->fields('name') - ->query($family_name) - ->getQuery() - ->whereIn('id', $pri_carers) - ->pluck('family_id') - ->toArray(); - } else { - // We may not be able to use Searchy, so we default to unfuzzy. - $filtered_family_ids = $this->exactSearch($family_name, $pri_carers); - } + Log::info(sprintf('Registration %s updated by service user %s', $registration->id, Auth::id())); - return $filtered_family_ids; + return redirect() + ->route('store.registration.edit', $registration) + ->with('message', 'Registration updated.'); } } From a04e8c5e3364e18e285f84c177ce909c89c06ce1 Mon Sep 17 00:00:00 2001 From: charles strange Date: Mon, 30 Mar 2026 12:02:46 +0100 Subject: [PATCH 033/168] fix: rules and tests --- app/Http/Controllers/Store/RegistrationController.php | 3 +-- app/Http/Requests/StoreUpdateRegistrationRequest.php | 5 +++-- tests/Feature/Store/RegistrationPageTest.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Store/RegistrationController.php b/app/Http/Controllers/Store/RegistrationController.php index ff02d58ec..a5e6be796 100644 --- a/app/Http/Controllers/Store/RegistrationController.php +++ b/app/Http/Controllers/Store/RegistrationController.php @@ -614,10 +614,9 @@ static function (string $name): Carer { $children = $this->makeChildrenFromInput((array)($data['children'] ?? [])); - // Preserve eligible_from if already set; only stamp it on first transition $eligibleFrom = ($data['eligibility-hsbs'] === 'healthy-start-receiving' && !$registration->eligible_from) ? Carbon::now() - : $registration->eligible_from; + : null; try { DB::transaction(static function () use ( diff --git a/app/Http/Requests/StoreUpdateRegistrationRequest.php b/app/Http/Requests/StoreUpdateRegistrationRequest.php index c5ceef41a..6ad47983d 100644 --- a/app/Http/Requests/StoreUpdateRegistrationRequest.php +++ b/app/Http/Requests/StoreUpdateRegistrationRequest.php @@ -38,8 +38,8 @@ public function rules() // Element MUST be present; MUST be a not-null string 'pri_carer.*' => 'required|string', // May be nullable, MUST be a standard - 'pri_carer_email' => 'nullable|email:rfc', - 'pri_carer_telno' => 'nullable|phone:GB', + 'pri_carer_email.*' => 'nullable|email:rfc', + 'pri_carer_telno.*' => 'nullable|phone:GB', // MAY be present; MUST be a not-null string 'sec_carers' => 'array|min:1', 'sec_carers.*' => 'string', @@ -54,6 +54,7 @@ public function rules() 'children.*.dob' => 'required_if:children.*.verified,=,true|date_format:Y-m', // MAY be present; MUST be a boolean 'children.*.verified' => 'boolean', + 'children.*.deferred' => 'boolean', // SOMETIMES is present (SP doesn't have them) MUST be in listed states 'eligibility-hsbs' => [ 'sometimes', diff --git a/tests/Feature/Store/RegistrationPageTest.php b/tests/Feature/Store/RegistrationPageTest.php index 81eb35941..f551ca9f3 100644 --- a/tests/Feature/Store/RegistrationPageTest.php +++ b/tests/Feature/Store/RegistrationPageTest.php @@ -260,7 +260,7 @@ public function testSelectingNotReceivingHSPutsNullInTable(): void public function testChangingToNotReceivingHSPutsNullInTable(): void { - $this->assertEquals(0, Registration::get()->count()); + $this->assertEquals(0, Registration::count()); $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) ->type('Test Carer', 'pri_carer') @@ -270,7 +270,7 @@ public function testChangingToNotReceivingHSPutsNullInTable(): void ->press('Save Family') ->seePageIs(URL::route('store.registration.edit', [ 'registration' => 1 ])) ; - $this->assertEquals(1, Registration::get()->count()); + $this->assertEquals(1, Registration::count()); $registration = Registration::first(); $this->assertNotNull($registration->eligible_from); From accfd743f34f62f39be71943df46d665baf36645 Mon Sep 17 00:00:00 2001 From: charles strange Date: Mon, 30 Mar 2026 16:41:20 +0100 Subject: [PATCH 034/168] fix: add specific tests --- .../Store/RegistrationController.php | 10 +- app/View/Components/PasswordInput.php | 2 +- .../store/partials/pri-carer_fields.blade.php | 6 +- tests/Feature/Store/EditPageTest.php | 286 ++++++++++++++++++ tests/Feature/Store/RegistrationPageTest.php | 149 ++++++++- 5 files changed, 432 insertions(+), 21 deletions(-) diff --git a/app/Http/Controllers/Store/RegistrationController.php b/app/Http/Controllers/Store/RegistrationController.php index a5e6be796..ebd91937a 100644 --- a/app/Http/Controllers/Store/RegistrationController.php +++ b/app/Http/Controllers/Store/RegistrationController.php @@ -575,14 +575,14 @@ public function update(StoreUpdateRegistrationRequest $request, Registration $re // emailsecure and telnosecure are special $priEmail = $data['pri_carer_email'][$priCarerId] ?? null; - if ($priEmail !== null && $priCarer->language !== $priEmail) { - $priCarer->emailsecure = $priEmail; + if ($priEmail !== null && $priCarer->emailsecret->reveal() !== $priEmail) { + $priCarer->emailsecret = $priEmail; $amendedCarers[] = $priCarer; } - $priTelno = $data['pri_carer_email'][$priCarerId] ?? null; - if ($priTelno !== null && $priCarer->language !== $priTelno) { - $priCarer->telnosecure = $priTelno; + $priTelno = $data['pri_carer_telno'][$priCarerId] ?? null; + if ($priTelno !== null && $priCarer->telnosecret->reveal() !== $priTelno) { + $priCarer->telnosecret = $priTelno; $amendedCarers[] = $priCarer; } diff --git a/app/View/Components/PasswordInput.php b/app/View/Components/PasswordInput.php index f46bff027..1b5d5adf9 100644 --- a/app/View/Components/PasswordInput.php +++ b/app/View/Components/PasswordInput.php @@ -76,7 +76,7 @@ public function render(): View // submission). A failed submission must always show the old() value // unmasked so the user can correct it. $oldValue = old($oldKey); - $isPrefilled = $this->existingPassword && $oldValue === null; + $isPrefilled = $this->existingPassword && ($oldValue === null); // ── Display value ───────────────────────────────────────────────── // Phantom state: field is visually empty (bullets come from placeholder). diff --git a/resources/views/store/partials/pri-carer_fields.blade.php b/resources/views/store/partials/pri-carer_fields.blade.php index 245251258..78eaacc16 100644 --- a/resources/views/store/partials/pri-carer_fields.blade.php +++ b/resources/views/store/partials/pri-carer_fields.blade.php @@ -33,15 +33,13 @@
diff --git a/tests/Feature/Store/EditPageTest.php b/tests/Feature/Store/EditPageTest.php index 629a6702e..07657ab01 100644 --- a/tests/Feature/Store/EditPageTest.php +++ b/tests/Feature/Store/EditPageTest.php @@ -612,4 +612,290 @@ public function testICanDeleteASecondaryCarerWhoHasCollectedABundle(): void ->see('Full Collection History') ; } + + /** + * The email input is always present on the edit page. + * x-password-input renders type="text" regardless of state. + */ + public function testItShowsAPrimaryCarerEmailInput(): void + { + $this->actingAs($this->centreUser, 'store') + ->visit(URL::route('store.registration.edit', ['registration' => $this->registration])) + ->seeElement('input[id="pri_carer_email"][type="text"]'); + } + + /** + * The telephone input is always present on the edit page. + */ + public function testItShowsAPrimaryCarerTelnoInput(): void + { + $this->actingAs($this->centreUser, 'store') + ->visit(URL::route('store.registration.edit', ['registration' => $this->registration])) + ->seeElement('input[id="pri_carer_telno"][type="text"]'); + } + + + // ── Phantom-prefill rendering ──────────────────────────────────────────── + + /** + * When the primary carer already has an email address the email input must + * render in phantom-prefill state: + * • name attribute is empty (nothing submitted → backend leaves it unchanged) + * • placeholder is "••••••••" (visual cue that a value exists) + * • data-pw-prefilled attribute is present (JS hook) + */ + public function testEmailInputIsInPhantomPrefillStateWhenCarerHasAnExistingEmail(): void + { + $priCarer = $this->registration->family->carers->first(); + $priCarer->emailsecret = 'existing@example.com'; + $priCarer->save(); + + $this->actingAs($this->centreUser, 'store') + ->visit(URL::route('store.registration.edit', ['registration' => $this->registration])) + ->seeElement('input[id="pri_carer_email"][name=""][data-pw-prefilled][placeholder="••••••••"]'); + } + + /** + * When the primary carer already has a telephone number the telno input + * must also render in phantom-prefill state. + */ + public function testTelnoInputIsInPhantomPrefillStateWhenCarerHasAnExistingTelno(): void + { + $priCarer = $this->registration->family->carers->first(); + $priCarer->telnosecret = '7400123456'; + $priCarer->save(); + + $this->actingAs($this->centreUser, 'store') + ->visit(URL::route('store.registration.edit', ['registration' => $this->registration])) + ->seeElement('input[id="pri_carer_telno"][name=""][data-pw-prefilled][placeholder="••••••••"]'); + } + + /** + * When the primary carer has no existing email the email input must render + * in virgin state — name is set to the array-keyed field name so the value + * is submitted normally. + */ + public function testEmailInputIsInVirginStateWhenCarerHasNoExistingEmail(): void + { + $priCarer = $this->registration->family->carers->first(); + // Confirm the factory left emailsecret null (virgin starting point). + $this->assertEmpty($priCarer->emailsecret->reveal()); + + $expectedName = 'pri_carer_email[' . $priCarer->id . ']'; + + $this->actingAs($this->centreUser, 'store') + ->visit(URL::route('store.registration.edit', ['registration' => $this->registration])) + ->seeElement('input[id="pri_carer_email"][name="' . $expectedName . '"]') + ->dontSeeElement('input[id="pri_carer_email"][data-pw-prefilled]'); + } + + /** + * data-pw-name is rendered whenever existingPassword is true so that the + * JS Escape handler can revert from virgin → phantom. It must carry the + * resolved array-style field name. + */ + public function testEmailInputExposesDataPwNameForEscapeRevertWhenCarerHasExistingEmail(): void + { + $priCarer = $this->registration->family->carers->first(); + $priCarer->emailsecret = 'existing@example.com'; + $priCarer->save(); + + $expectedName = 'pri_carer_email[' . $priCarer->id . ']'; + + $this->actingAs($this->centreUser, 'store') + ->visit(URL::route('store.registration.edit', ['registration' => $this->registration])) + ->seeElement('input[id="pri_carer_email"][data-pw-name="' . $expectedName . '"]'); + } + + + // ── Successful update ──────────────────────────────────────────────────── + + /** + * Submitting a new valid email for the primary carer must persist it. + * The field is submitted as pri_carer_email[{carer_id}]. + * + * NOTE: this test will fail until the bug in update() is fixed — the + * property assignment uses ->emailsecure instead of ->emailsecret (or + * whichever name matches the actual DB column used by store()). + */ + public function testItCanUpdateThePrimaryCarerEmail(): void + { + $priCarer = $this->registration->family->carers->first(); + $this->assertEmpty($priCarer->emailsecret->reveal()); + + $data = [ + 'pri_carer' => [$priCarer->id => $priCarer->name], + 'pri_carer_email' => [$priCarer->id => 'updated@example.com'], + 'eligibility-hsbs' => $this->registration->eligibility_hsbs, + 'eligibility-nrpf' => $this->registration->eligibility_nrpf, + ]; + + $this->actingAs($this->centreUser, 'store') + ->call('PUT', route('store.registration.update', $this->registration->id), $data); + $this->assertResponseStatus(302); + + $priCarer = Carer::find($priCarer->id); + $this->assertEquals('updated@example.com', $priCarer->emailsecret->reveal()); + } + + /** + * Submitting a new valid GB telephone number for the primary carer must + * persist it. + * + * NOTE: this test will fail until two bugs in update() are fixed: + * (a) $priTelno reads from $data['pri_carer_email'] instead of + * $data['pri_carer_telno']. + * (b) The property assignment uses ->telnosecure instead of ->telnosecret + * (or whichever name matches the actual DB column). + */ + public function testItCanUpdateThePrimaryCarerTelno(): void + { + $priCarer = $this->registration->family->carers->first(); + $this->assertEmpty($priCarer->telnosecret->reveal()); + + $data = [ + 'pri_carer' => [$priCarer->id => $priCarer->name], + 'pri_carer_telno' => [$priCarer->id => '7400123456'], + 'eligibility-hsbs' => $this->registration->eligibility_hsbs, + 'eligibility-nrpf' => $this->registration->eligibility_nrpf, + ]; + + $this->actingAs($this->centreUser, 'store') + ->call('PUT', route('store.registration.update', $this->registration->id), $data); + $this->assertResponseStatus(302); + + $priCarer = Carer::find($priCarer->id); + $this->assertEquals('7400123456', $priCarer->telnosecret->reveal()); + } + + /** + * A carer can have both email and telephone updated in a single request. + */ + public function testItCanUpdateBothEmailAndTelnoTogether(): void + { + $priCarer = $this->registration->family->carers->first(); + + $data = [ + 'pri_carer' => [$priCarer->id => $priCarer->name], + 'pri_carer_email' => [$priCarer->id => 'both@example.com'], + 'pri_carer_telno' => [$priCarer->id => '7400123456'], + 'eligibility-hsbs' => $this->registration->eligibility_hsbs, + 'eligibility-nrpf' => $this->registration->eligibility_nrpf, + ]; + + $this->actingAs($this->centreUser, 'store') + ->call('PUT', route('store.registration.update', $this->registration->id), $data); + $this->assertResponseStatus(302); + + $priCarer = Carer::find($priCarer->id); + $this->assertEquals('both@example.com', $priCarer->emailsecret->reveal()); + $this->assertEquals('7400123456', $priCarer->telnosecret->reveal()); + } + + + // ── Phantom-prefill "absent = unchanged" contract ──────────────────────── + + /** + * When the email field is absent from the PUT payload (simulating the + * phantom-prefill state where name="" so nothing is submitted), the + * existing email on the carer must not be overwritten. + */ + public function testAbsentEmailFieldDoesNotOverwriteExistingEmail(): void + { + $priCarer = $this->registration->family->carers->first(); + $priCarer->emailsecret = 'keep-this@example.com'; + $priCarer->save(); + + // No pri_carer_email key — mirrors what the browser sends when the + // field is in phantom-prefill state (name is suppressed). + $data = [ + 'pri_carer' => [$priCarer->id => $priCarer->name], + 'eligibility-hsbs' => $this->registration->eligibility_hsbs, + 'eligibility-nrpf' => $this->registration->eligibility_nrpf, + ]; + + $this->actingAs($this->centreUser, 'store') + ->call('PUT', route('store.registration.update', $this->registration->id), $data); + $this->assertResponseStatus(302); + + $priCarer = Carer::find($priCarer->id); + $this->assertEquals('keep-this@example.com', $priCarer->emailsecret->reveal()); + } + + /** + * Same contract for the telephone number field. + */ + public function testAbsentTelnoFieldDoesNotOverwriteExistingTelno(): void + { + $priCarer = $this->registration->family->carers->first(); + $priCarer->telnosecret = '7400123456'; + $priCarer->save(); + + $data = [ + 'pri_carer' => [$priCarer->id => $priCarer->name], + 'eligibility-hsbs' => $this->registration->eligibility_hsbs, + 'eligibility-nrpf' => $this->registration->eligibility_nrpf, + ]; + + $this->actingAs($this->centreUser, 'store') + ->call('PUT', route('store.registration.update', $this->registration->id), $data); + $this->assertResponseStatus(302); + + $priCarer = Carer::find($priCarer->id); + $this->assertEquals('7400123456', $priCarer->telnosecret->reveal()); + } + + + // ── Validation rejection ───────────────────────────────────────────────── + + /** + * An invalid email address in the PUT payload must be rejected. + * Laravel redirects back (302) with validation errors in the session; + * the carer record must remain unchanged. + */ + public function testItRejectsAnInvalidEmailOnUpdate(): void + { + $priCarer = $this->registration->family->carers->first(); + + $data = [ + 'pri_carer' => [$priCarer->id => $priCarer->name], + 'pri_carer_email' => [$priCarer->id => 'not-a-valid-email'], + 'eligibility-hsbs' => $this->registration->eligibility_hsbs, + 'eligibility-nrpf' => $this->registration->eligibility_nrpf, + ]; + + $this->actingAs($this->centreUser, 'store') + ->call('PUT', route('store.registration.update', $this->registration->id), $data); + + // Validation failure on a web route redirects back rather than 422. + $this->assertResponseStatus(302); + + // The carer must not have gained a bad email value. + $priCarer = Carer::find($priCarer->id); + $this->assertEmpty($priCarer->emailsecret->reveal()); + } + + /** + * An invalid GB telephone number in the PUT payload must be rejected and + * must not be stored on the carer. + */ + public function testItRejectsAnInvalidTelnoOnUpdate(): void + { + $priCarer = $this->registration->family->carers->first(); + + $data = [ + 'pri_carer' => [$priCarer->id => $priCarer->name], + 'pri_carer_telno' => [$priCarer->id => 'not-a-phone-number'], + 'eligibility-hsbs' => $this->registration->eligibility_hsbs, + 'eligibility-nrpf' => $this->registration->eligibility_nrpf, + ]; + + $this->actingAs($this->centreUser, 'store') + ->call('PUT', route('store.registration.update', $this->registration->id), $data); + + $this->assertResponseStatus(302); + + $priCarer = Carer::find($priCarer->id); + $this->assertEmpty($priCarer->telnosecret->reveal()); + } } diff --git a/tests/Feature/Store/RegistrationPageTest.php b/tests/Feature/Store/RegistrationPageTest.php index f551ca9f3..a072cf405 100644 --- a/tests/Feature/Store/RegistrationPageTest.php +++ b/tests/Feature/Store/RegistrationPageTest.php @@ -158,7 +158,7 @@ public function testLogoDoesntRedirectMeToDashboard(): void public function testItCanSaveARegistration(): void { // There are no registrations - $this->assertEquals(0, Registration::get()->count()); + $this->assertEquals(0, Registration::count()); $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) @@ -171,7 +171,7 @@ public function testItCanSaveARegistration(): void ; // There is now a Registration. - $this->assertEquals(1, Registration::get()->count()); + $this->assertEquals(1, Registration::count()); $registration = Registration::find(1); @@ -187,7 +187,7 @@ public function testItCanSaveARegistration(): void public function testItRequiresConsentToSave(): void { // There are no registrations - $this->assertEquals(0, Registration::get()->count()); + $this->assertEquals(0, Registration::count()); $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) @@ -199,14 +199,14 @@ public function testItRequiresConsentToSave(): void ; // There is still not a Registration. - $this->assertEquals(0, Registration::get()->count()); + $this->assertEquals(0, Registration::count()); } public function testItRequiresAPrimaryCarerToSave(): void { // There are no registrations - $this->assertEquals(0, Registration::get()->count()); + $this->assertEquals(0, Registration::count()); $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) @@ -218,7 +218,7 @@ public function testItRequiresAPrimaryCarerToSave(): void ; // There is still not a Registration. - $this->assertEquals(0, Registration::get()->count()); + $this->assertEquals(0, Registration::count()); } @@ -234,7 +234,7 @@ public function testSelectingReceivingHSPutsDateInTable(): void ->press('Save Family') ->seePageIs(URL::route('store.registration.edit', [ 'registration' => 1 ])) ; - $this->assertEquals(1, Registration::get()->count()); + $this->assertEquals(1, Registration::count()); $registration = Registration::first(); $this->assertNotNull($registration->eligible_from); } @@ -242,7 +242,7 @@ public function testSelectingReceivingHSPutsDateInTable(): void public function testSelectingNotReceivingHSPutsNullInTable(): void { - $this->assertEquals(0, Registration::get()->count()); + $this->assertEquals(0, Registration::count()); $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) ->type('Test Carer', 'pri_carer') @@ -252,7 +252,7 @@ public function testSelectingNotReceivingHSPutsNullInTable(): void ->press('Save Family') ->seePageIs(URL::route('store.registration.edit', [ 'registration' => 1 ])) ; - $this->assertEquals(1, Registration::get()->count()); + $this->assertEquals(1, Registration::count()); $registration = Registration::first(); $this->assertNull($registration->eligible_from); } @@ -287,7 +287,7 @@ public function testChangingToNotReceivingHSPutsNullInTable(): void public function testUpdatingOtherFieldsDoesNotChangeEligibiltyDate(): void { - $this->assertEquals(0, Registration::get()->count()); + $this->assertEquals(0, Registration::count()); $this->actingAs($this->centreUser, 'store') ->visit(URL::route('store.registration.create')) ->type('Test Carer', 'pri_carer') @@ -297,7 +297,7 @@ public function testUpdatingOtherFieldsDoesNotChangeEligibiltyDate(): void ->press('Save Family') ->seePageIs(URL::route('store.registration.edit', [ 'registration' => 1 ])) ; - $this->assertEquals(1, Registration::get()->count()); + $this->assertEquals(1, Registration::count()); $registration = Registration::first(); // SP allows this field to be null so test needed changing to accommodate this if ($this->assertNotNull($registration->eligible_from)) { @@ -331,4 +331,131 @@ public function testAsAnSPUserICanSeeTheCorrectInputs(): void ->see('Save Household') ; } + + + // ── Presence on page ──────────────────────────────────────────────────────── + + /** + * The email field is rendered by x-password-input, which always uses + * type="text". On create there is no existing value, so existingPassword + * is false and the field renders in virgin state: id and name both equal + * "pri_carer_email". + */ + public function testItShowsAPrimaryCarerEmailInput(): void + { + $this->actingAs($this->centreUser, 'store') + ->visit(URL::route('store.registration.create')) + ->seeElement('input[id="pri_carer_email"][name="pri_carer_email"][type="text"]'); + } + + /** + * Same reasoning as the email field above. + */ + public function testItShowsAPrimaryCarerTelnoInput(): void + { + $this->actingAs($this->centreUser, 'store') + ->visit(URL::route('store.registration.create')) + ->seeElement('input[id="pri_carer_telno"][name="pri_carer_telno"][type="text"]'); + } + + + // ── Successful save with optional contact fields ───────────────────────── + + /** + * A valid RFC email address and a valid GB telephone number are both + * accepted and persisted on the primary Carer record. + */ + public function testItCanSaveARegistrationWithEmailAndTelno(): void + { + $this->assertEquals(0, Registration::count()); + + $this->actingAs($this->centreUser, 'store') + ->visit(URL::route('store.registration.create')) + ->type('Test Carer', 'pri_carer') + ->type('testcarer@example.com', 'pri_carer_email') + ->type('7400123456', 'pri_carer_telno') + ->select('healthy-start-applying', 'eligibility-hsbs') + ->select('no', 'eligibility-nrpf') + ->check('consent') + ->press('Save Family'); + + + $this->seePageIs(URL::route('store.registration.edit', ['registration' => 1])); + + $this->assertEquals(1, Registration::count()); + + $carer = Registration::find(1)->family->carers->first(); + $this->assertEquals('testcarer@example.com', $carer->emailsecret->reveal()); + $this->assertEquals('7400123456', $carer->telnosecret->reveal()); + } + + /** + * Both contact fields are nullable — omitting them entirely must not + * prevent registration and must leave the Carer columns null. + */ + public function testItAcceptsBlankEmailAndTelnoOnSave(): void + { + $this->assertEquals(0, Registration::count()); + + $this->actingAs($this->centreUser, 'store') + ->visit(URL::route('store.registration.create')) + ->type('Test Carer', 'pri_carer') + ->select('healthy-start-applying', 'eligibility-hsbs') + ->select('no', 'eligibility-nrpf') + ->check('consent') + ->press('Save Family') + ->seePageIs(URL::route('store.registration.edit', ['registration' => 1])); + + $this->assertEquals(1, Registration::count()); + + $carer = Registration::find(1)->family->carers->first(); + $this->assertNull($carer->emailsecret->reveal()); + $this->assertNull($carer->telnosecret->reveal()); + } + + + // ── Validation rejection ───────────────────────────────────────────────── + + /** + * A string that is not a valid RFC email address must be rejected and + * the registration must not be created. + */ + public function testItRejectsAnInvalidEmailOnSave(): void + { + $this->assertEquals(0, Registration::count()); + + $this->actingAs($this->centreUser, 'store') + ->visit(URL::route('store.registration.create')) + ->type('Test Carer', 'pri_carer') + ->type('not-a-valid-email', 'pri_carer_email') + ->select('healthy-start-applying', 'eligibility-hsbs') + ->select('no', 'eligibility-nrpf') + ->check('consent') + ->press('Save Family') + ->seePageIs(URL::route('store.registration.create')); + + $this->assertEquals(0, Registration::count()); + } + + /** + * A string that is not a valid GB phone number must be rejected and + * the registration must not be created. + * The validation rule is nullable|phone:GB from the libphonenumber wrapper. + */ + public function testItRejectsAnInvalidTelnoOnSave(): void + { + $this->assertEquals(0, Registration::count()); + + $this->actingAs($this->centreUser, 'store') + ->visit(URL::route('store.registration.create')) + ->type('Test Carer', 'pri_carer') + ->type('not-a-phone-number', 'pri_carer_telno') + ->select('healthy-start-applying', 'eligibility-hsbs') + ->select('no', 'eligibility-nrpf') + ->check('consent') + ->press('Save Family') + ->seePageIs(URL::route('store.registration.create')); + + $this->assertEquals(0, Registration::count()); + } } From b8f11052da4ab50e47dbabc2442aadfd284a7844 Mon Sep 17 00:00:00 2001 From: charles strange Date: Mon, 30 Mar 2026 21:57:39 +0100 Subject: [PATCH 035/168] fix: field validation for standard programme --- .../Store/RegistrationController.php | 21 +++++++++++-------- .../Requests/StoreNewRegistrationRequest.php | 10 ++++++++- .../StoreUpdateRegistrationRequest.php | 8 +++++++ app/View/Components/GeneralInput.php | 3 --- .../views/components/general-input.blade.php | 2 +- .../partials/input-filter.blade.php | 4 ---- .../views/components/password-input.blade.php | 2 +- 7 files changed, 31 insertions(+), 19 deletions(-) diff --git a/app/Http/Controllers/Store/RegistrationController.php b/app/Http/Controllers/Store/RegistrationController.php index ebd91937a..fd404b772 100644 --- a/app/Http/Controllers/Store/RegistrationController.php +++ b/app/Http/Controllers/Store/RegistrationController.php @@ -299,7 +299,7 @@ public function edit(Registration $registration): View|Factory|Application /** @var Valuation $valuation */ $valuation = $registration->getValuation(); - // Grab carers copy for shift)ing without altering family->carers + // Grab carers copy for shifting without altering family->carers $carers = $registration->family->carers->all(); $pri_carer = array_shift($carers); $pri_carer_ethnicity = $pri_carer->ethnicity; @@ -496,7 +496,7 @@ public function store(StoreNewRegistrationRequest $request): RedirectResponse 'ethnicity' => $data['pri_carer_ethnicity'] ?? null, 'language' => $data['pri_carer_language'] ?? null, 'telnosecret' => $data['pri_carer_telno'] ?? null, - 'emailsecret' => $data['pri_carer_email'] ?? null + 'emailsecret' => $data['pri_carer_email'] ?? null, ]); }, (array)($data['pri_carer'] ?? [])), array_map( @@ -511,9 +511,9 @@ static function (string $name): Carer { $registration = new Registration([ 'consented_on' => Carbon::now(), - 'eligibility_hsbs' => $data['eligibility-hsbs'], - 'eligibility_nrpf' => $data['eligibility-nrpf'], - 'eligible_from' => $data['eligibility-hsbs'] === 'healthy-start-receiving' ? Carbon::now() : null, + 'eligibility_hsbs' => $data['eligibility-hsbs'] ?? null, + 'eligibility_nrpf' => $data['eligibility-nrpf'] ?? null, + 'eligible_from' => ($data['eligibility-hsbs'] ?? null) === 'healthy-start-receiving' ? Carbon::now() : null, ]); $family = new Family(); @@ -614,7 +614,10 @@ static function (string $name): Carer { $children = $this->makeChildrenFromInput((array)($data['children'] ?? [])); - $eligibleFrom = ($data['eligibility-hsbs'] === 'healthy-start-receiving' && !$registration->eligible_from) + $eligibleFrom = ( + ($data['eligibility-hsbs'] ?? null) === 'healthy-start-receiving' && + !$registration->eligible_from + ) ? Carbon::now() : null; @@ -641,9 +644,9 @@ static function (string $name): Carer { }); $registration->fill([ - 'eligibility_hsbs' => $data['eligibility-hsbs'], - 'eligibility_nrpf' => $data['eligibility-nrpf'], - 'eligible_from' => $eligibleFrom + 'eligibility_hsbs' => $data['eligibility-hsbs'] ?? null, + 'eligibility_nrpf' => $data['eligibility-nrpf'] ?? null, + 'eligible_from' => $eligibleFrom, ])->save(); }); } catch (Throwable $e) { diff --git a/app/Http/Requests/StoreNewRegistrationRequest.php b/app/Http/Requests/StoreNewRegistrationRequest.php index 1f2c12d23..78fc19cad 100644 --- a/app/Http/Requests/StoreNewRegistrationRequest.php +++ b/app/Http/Requests/StoreNewRegistrationRequest.php @@ -34,6 +34,14 @@ public function rules() 'consent' => 'required|accepted', // MUST be present; MUST be a not-null string 'pri_carer' => 'required|string', + 'pri_carer_ethnicity' => [ + 'nullable', + Rule::in(array_keys(config('arc.ethnicity_desc'))) + ], + 'pri_carer_language' => [ + 'not-regex:/^.*[\p{C}].*$/u', + 'regex:/^[A-Za-z.\s\'—-]+$/', + ], // May be nullable, MUST be a standard 'pri_carer_email' => 'nullable|email:rfc', 'pri_carer_telno' => 'nullable|phone:GB', @@ -50,7 +58,7 @@ public function rules() 'children.*.dob' => 'required_if:children.*.verified,=,true|date_format:Y-m', // MAY be present; MUST be a boolean 'children.*.verified' => 'boolean', - 'is_pri_carer' => 'boolean', + 'children.*.is_pri_carer' => 'boolean', // SOMETIMES is present (SP doesn't have them) MUST be in listed states 'eligibility-hsbs' => [ 'sometimes', diff --git a/app/Http/Requests/StoreUpdateRegistrationRequest.php b/app/Http/Requests/StoreUpdateRegistrationRequest.php index 6ad47983d..b065f0b58 100644 --- a/app/Http/Requests/StoreUpdateRegistrationRequest.php +++ b/app/Http/Requests/StoreUpdateRegistrationRequest.php @@ -40,6 +40,14 @@ public function rules() // May be nullable, MUST be a standard 'pri_carer_email.*' => 'nullable|email:rfc', 'pri_carer_telno.*' => 'nullable|phone:GB', + 'pri_carer_ethnicity.*' => [ + 'required', + Rule::in(array_keys(config('arc.ethnicity_desc'))) + ], + 'pri_carer_language.*' => [ + 'not-regex:/^.*[\p{C}].*$/u', + 'regex:/^[A-Za-z.\s\'—-]+$/', + ], // MAY be present; MUST be a not-null string 'sec_carers' => 'array|min:1', 'sec_carers.*' => 'string', diff --git a/app/View/Components/GeneralInput.php b/app/View/Components/GeneralInput.php index 37d17ad97..5a077798e 100644 --- a/app/View/Components/GeneralInput.php +++ b/app/View/Components/GeneralInput.php @@ -30,9 +30,6 @@ class GeneralInput extends Component * @param string|null $alertId Optional id attribute forwarded to the errors partial * (matches the existing 'id' => 'carer-alert' usage). * @param string|null $filter Named sanitiser applied via JS on the 'input' event. - * Supported values: 'alpha-space' (/[^a-z ]/ — strips - * anything that isn't a lowercase letter or space, which - * is what the language fields currently do inline). * @param string|null $placeholder Optional placeholder text. */ public function __construct( diff --git a/resources/views/components/general-input.blade.php b/resources/views/components/general-input.blade.php index 11cc4cc70..7c2182edb 100644 --- a/resources/views/components/general-input.blade.php +++ b/resources/views/components/general-input.blade.php @@ -13,7 +13,7 @@ name – base field name, used for id= and label for= label – visible label text type – input type, default 'text' - filter – named JS sanitiser ('alpha-space' supported) + filter – named JS sanitiser placeholder – optional placeholder text alertId – optional id forwarded to the errors partial --}} diff --git a/resources/views/components/partials/input-filter.blade.php b/resources/views/components/partials/input-filter.blade.php index f5744c743..8a6151392 100644 --- a/resources/views/components/partials/input-filter.blade.php +++ b/resources/views/components/partials/input-filter.blade.php @@ -7,10 +7,6 @@ The @once guard ensures the listener is registered exactly once no matter how many components on the page include this file. - Supported filter names → regex applied on every 'input' event: - alpha-space strips anything that is not a letter or space - replicates: value.replace(/[^a-zA-Z ]/, '') - To add a new filter, add an entry to the FILTERS map below. --}} @once('input-filter-js') diff --git a/resources/views/components/password-input.blade.php b/resources/views/components/password-input.blade.php index 268a2c866..f9aaa02b3 100644 --- a/resources/views/components/password-input.blade.php +++ b/resources/views/components/password-input.blade.php @@ -28,7 +28,7 @@ ── Props (declared here — consumed directly from the blade tag) ────────── name – base field name, used for id= and label for= label – visible label text - filter – named JS sanitiser ('alpha-space' supported) + filter – named JS sanitiser placeholder – placeholder for virgin state only alertId – optional id forwarded to the errors partial From 81e72e521a94b3f4f13c6a906ce1d88933030834 Mon Sep 17 00:00:00 2001 From: charles strange Date: Mon, 30 Mar 2026 22:56:26 +0100 Subject: [PATCH 036/168] fix: field validation for sp programme --- app/Http/Controllers/Store/RegistrationController.php | 3 +-- app/Http/Requests/StoreNewRegistrationRequest.php | 7 +++++-- app/Http/Requests/StoreUpdateRegistrationRequest.php | 7 +++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Store/RegistrationController.php b/app/Http/Controllers/Store/RegistrationController.php index fd404b772..4b9b838a8 100644 --- a/app/Http/Controllers/Store/RegistrationController.php +++ b/app/Http/Controllers/Store/RegistrationController.php @@ -260,7 +260,6 @@ static function ($child): Child { if (array_key_exists('is_pri_carer', $child)) { $is_pri_carer = (bool)$child['is_pri_carer']; } - return new Child([ 'born' => $month_of_birth->isPast(), 'dob' => $month_of_birth->toDateTimeString(), @@ -573,7 +572,7 @@ public function update(StoreUpdateRegistrationRequest $request, Registration $re $amendedCarers[] = $priCarer; } - // emailsecure and telnosecure are special + // emailsecret and telnosecret are special $priEmail = $data['pri_carer_email'][$priCarerId] ?? null; if ($priEmail !== null && $priCarer->emailsecret->reveal() !== $priEmail) { $priCarer->emailsecret = $priEmail; diff --git a/app/Http/Requests/StoreNewRegistrationRequest.php b/app/Http/Requests/StoreNewRegistrationRequest.php index 78fc19cad..ba062805d 100644 --- a/app/Http/Requests/StoreNewRegistrationRequest.php +++ b/app/Http/Requests/StoreNewRegistrationRequest.php @@ -35,10 +35,13 @@ public function rules() // MUST be present; MUST be a not-null string 'pri_carer' => 'required|string', 'pri_carer_ethnicity' => [ - 'nullable', - Rule::in(array_keys(config('arc.ethnicity_desc'))) + Rule::in(array_merge( + [0, '0'], + array_keys(config('arc.ethnicity_desc')) + )) ], 'pri_carer_language' => [ + 'nullable', 'not-regex:/^.*[\p{C}].*$/u', 'regex:/^[A-Za-z.\s\'—-]+$/', ], diff --git a/app/Http/Requests/StoreUpdateRegistrationRequest.php b/app/Http/Requests/StoreUpdateRegistrationRequest.php index b065f0b58..0ddfd72b2 100644 --- a/app/Http/Requests/StoreUpdateRegistrationRequest.php +++ b/app/Http/Requests/StoreUpdateRegistrationRequest.php @@ -41,10 +41,13 @@ public function rules() 'pri_carer_email.*' => 'nullable|email:rfc', 'pri_carer_telno.*' => 'nullable|phone:GB', 'pri_carer_ethnicity.*' => [ - 'required', - Rule::in(array_keys(config('arc.ethnicity_desc'))) + Rule::in(array_merge( + [0, '0'], + array_keys(config('arc.ethnicity_desc')) + )) ], 'pri_carer_language.*' => [ + 'nullable', 'not-regex:/^.*[\p{C}].*$/u', 'regex:/^[A-Za-z.\s\'—-]+$/', ], From 3457ed059401a460d90303fd7389eb6a1c714815 Mon Sep 17 00:00:00 2001 From: charles strange Date: Mon, 30 Mar 2026 23:23:14 +0100 Subject: [PATCH 037/168] fix: primary carer age rating js fix --- app/Http/Requests/StoreNewRegistrationRequest.php | 1 + app/Http/Requests/StoreUpdateRegistrationRequest.php | 1 + resources/views/store/partials/householdSP.blade.php | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Http/Requests/StoreNewRegistrationRequest.php b/app/Http/Requests/StoreNewRegistrationRequest.php index ba062805d..9b07c4890 100644 --- a/app/Http/Requests/StoreNewRegistrationRequest.php +++ b/app/Http/Requests/StoreNewRegistrationRequest.php @@ -61,6 +61,7 @@ public function rules() 'children.*.dob' => 'required_if:children.*.verified,=,true|date_format:Y-m', // MAY be present; MUST be a boolean 'children.*.verified' => 'boolean', + 'children.*.deferred' => 'boolean', 'children.*.is_pri_carer' => 'boolean', // SOMETIMES is present (SP doesn't have them) MUST be in listed states 'eligibility-hsbs' => [ diff --git a/app/Http/Requests/StoreUpdateRegistrationRequest.php b/app/Http/Requests/StoreUpdateRegistrationRequest.php index 0ddfd72b2..1edf85473 100644 --- a/app/Http/Requests/StoreUpdateRegistrationRequest.php +++ b/app/Http/Requests/StoreUpdateRegistrationRequest.php @@ -66,6 +66,7 @@ public function rules() // MAY be present; MUST be a boolean 'children.*.verified' => 'boolean', 'children.*.deferred' => 'boolean', + 'children.*.is_pri_carer' => 'boolean', // SOMETIMES is present (SP doesn't have them) MUST be in listed states 'eligibility-hsbs' => [ 'sometimes', diff --git a/resources/views/store/partials/householdSP.blade.php b/resources/views/store/partials/householdSP.blade.php index 813d8eb99..ae447a439 100644 --- a/resources/views/store/partials/householdSP.blade.php +++ b/resources/views/store/partials/householdSP.blade.php @@ -29,7 +29,7 @@ {{ explode(',', $child->getAgeString())[0] }} is_pri_carer }} > Date: Tue, 31 Mar 2026 10:12:16 +0100 Subject: [PATCH 038/168] chore: switch to node 22/npm 20 --- .docker/entry-point.sh | 2 +- .nvmrc | 2 +- bin/makedeploy.sh | 7 +- docs/HOMESTEAD.md | 6 +- docs/README.md | 4 +- docs/STYLING.md | 6 +- package-lock.json | 10539 +++++++++++++++++++++++++++++++++++++++ package.json | 5 +- yarn.lock | 5475 -------------------- 9 files changed, 10554 insertions(+), 5492 deletions(-) create mode 100644 package-lock.json delete mode 100644 yarn.lock diff --git a/.docker/entry-point.sh b/.docker/entry-point.sh index 723edf7ef..d7beba4d3 100755 --- a/.docker/entry-point.sh +++ b/.docker/entry-point.sh @@ -52,7 +52,7 @@ function handleStartup() { fi done fi - yarn production + npm run production } checkDatabase diff --git a/.nvmrc b/.nvmrc index 2dbbe00e6..deed13c01 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.11.1 +lts/jod diff --git a/bin/makedeploy.sh b/bin/makedeploy.sh index c8b8d65c8..be26d615b 100755 --- a/bin/makedeploy.sh +++ b/bin/makedeploy.sh @@ -44,12 +44,11 @@ source ~/.bashrc nvm install nvm use -# Re-install Yarn dependencies -rm -rf ./node_modules -yarn install +# Re-install dependencies +npm ci # Build production CSS (or other production assets) -yarn prod +npm run prod # Reduce the size of vendor directory by installing only production dependencies rm -rf ./vendor diff --git a/docs/HOMESTEAD.md b/docs/HOMESTEAD.md index 1140fda26..9f030b6f4 100644 --- a/docs/HOMESTEAD.md +++ b/docs/HOMESTEAD.md @@ -57,9 +57,9 @@ Do the steps below: - [install nvm](https://github.com/nvm-sh/nvm#installing-and-updating) and then exit the vm (`exit` or `ctrl+D`) and re-enter it (`vagrant ssh` command) and navigate back to your ARCVService directory - `nvm install lts/carbon` - `nvm use` -- Install npm packages for webpack (JS and Sass) builds: `yarn install` -- Run `yarn dev` to make sure packages Store shares with Service have been included -- Compile Service from Sass with `yarn prod` +- Install npm packages for webpack (JS and Sass) builds: `npm run install` +- Run `npm run dev` to make sure packages Store shares with Service have been included +- Compile Service from Sass with `npm run prod` *** diff --git a/docs/README.md b/docs/README.md index 6813c885c..14a63b062 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,8 +20,8 @@ The service, market and store can be deployed, run locally for training/testing 9. `chmod 600 ./storage/*.key` to set permissions correctly 10. `php artisan passport:client --password --name="Rose Vouchers Password Grant Client" --provider=users` to set the client in the DB 11. Add the "password grant" client id and secret to your `.env` -12. Install packages for builds: `yarn install` -13. Run `yarn watch` in the background during development to automatically compile assets when modifying code or changing commit +12. Install packages for builds: `npm run install` +13. Run `npm run watch` in the background during development to automatically compile assets when modifying code or changing commit We suggest that you use the TLD `.test` as others, like `.app` may now be in the public domain and you will experience difficulty with respect to browser behavior over HTTP/HTTPS. diff --git a/docs/STYLING.md b/docs/STYLING.md index 1ff5f2843..eea40ce39 100644 --- a/docs/STYLING.md +++ b/docs/STYLING.md @@ -3,10 +3,10 @@ ## Service - Service styling is in `resources/assets/sass/app.scss` -- When amending the styles in development, switching to a new branch or pulling code, run `yarn watch` to watch for changes -- Service is compiled from Sass with `yarn prod` +- When amending the styles in development, switching to a new branch or pulling code, run `npm run watch` to watch for changes +- Service is compiled from Sass with `npm run prod` - ## Store - Store styling is in `public/store/css/main.css` -- Run `yarn dev` to make sure packages Store shares with Service have been included. +- Run `npm run dev` to make sure packages Store shares with Service have been included. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..172f75483 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,10539 @@ +{ + "name": "ARCVService", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "bootstrap-sass": "3.4.3", + "jquery": "3.7.1", + "jquery-ui": "1.14.2", + "laravel-mix": "6.0.49", + "resolve-url-loader": "5.0.0", + "sass": "^1.98.0", + "sass-loader": "16.0.7" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", + "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", + "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/clean-css": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.11.tgz", + "integrity": "sha512-Y8n81lQVTAfP2TOdtJJEsCoYl1AnOkqDqMvXb9/7pfgZZ7r8YrEyurrAvAoAjHOGXKRybay+5CsExqIH6liccw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "source-map": "^0.6.0" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express/node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/imagemin": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/imagemin/-/imagemin-9.0.1.tgz", + "integrity": "sha512-xMWpvrUhtYxl6EeW+UhVH3rwUKhCRx21XddcoWByjDAasXZT5pQaCn0YVnXoTijX5hlTrGqV4TGQL/Htpp00+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/imagemin-gifsicle": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/imagemin-gifsicle/-/imagemin-gifsicle-7.0.4.tgz", + "integrity": "sha512-ZghMBd/Jgqg5utTJNPmvf6DkuHzMhscJ8vgf/7MUGCpO+G+cLrhYltL+5d+h3A1B4W73S2SrmJZ1jS5LACpX+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/imagemin": "*" + } + }, + "node_modules/@types/imagemin-mozjpeg": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@types/imagemin-mozjpeg/-/imagemin-mozjpeg-8.0.4.tgz", + "integrity": "sha512-ZCAxV8SYJB8ehwHpnbRpHjg5Wc4HcyuAMiDhXbkgC7gujDoOTyHO3dhDkUtZ1oK1DLBRZapqG9etdLVhUml7yQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/imagemin": "*" + } + }, + "node_modules/@types/imagemin-optipng": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@types/imagemin-optipng/-/imagemin-optipng-5.2.4.tgz", + "integrity": "sha512-mvKnDMC8eCYZetAQudjs1DbgpR84WhsTx1wgvdiXnpuUEti3oJ+MaMYBRWPY0JlQ4+y4TXKOfa7+LOuT8daegQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/imagemin": "*" + } + }, + "node_modules/@types/imagemin-svgo": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@types/imagemin-svgo/-/imagemin-svgo-8.0.1.tgz", + "integrity": "sha512-YafkdrVAcr38U0Ln1C+L1n4SIZqC47VBHTyxCq7gTUSd1R9MdIvMcrljWlgU1M9O68WZDeQWUrKipKYfEOCOvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/imagemin": "*", + "@types/svgo": "^1" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/svgo": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@types/svgo/-/svgo-1.3.6.tgz", + "integrity": "sha512-AZU7vQcy/4WFEuwnwsNsJnFwupIpbllH1++LXScN6uxT1Z4zPzdrWG97w4/I7eFKFTvfy/bHFStWjdBAg2Vjug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "envinfo": "^7.7.3" + }, + "peerDependencies": { + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "webpack-cli": "4.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/assert": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.1.tgz", + "integrity": "sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "object.assign": "^4.1.4", + "util": "^0.10.4" + } + }, + "node_modules/assert/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/assert/node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-loader": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.4.1.tgz", + "integrity": "sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.4", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.12.tgz", + "integrity": "sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/bootstrap-sass": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/bootstrap-sass/-/bootstrap-sass-3.4.3.tgz", + "integrity": "sha512-vPgFnGMp1jWZZupOND65WS6mkR8rxhJxndT/AcMbqcq1hHMdkcH4sMPhznLzzoHOHkSCrd6J9F8pWBriPCKP2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.5.tgz", + "integrity": "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==", + "dev": true, + "license": "ISC", + "dependencies": { + "bn.js": "^5.2.2", + "browserify-rsa": "^4.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.6.1", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.9", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001782", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001782.tgz", + "integrity": "sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cipher-base": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", + "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/collect.js": { + "version": "4.36.1", + "resolved": "https://registry.npmjs.org/collect.js/-/collect.js-4.36.1.tgz", + "integrity": "sha512-jd97xWPKgHn6uvK31V6zcyPd40lUJd7gpYxbN2VOVxGWO4tyvS9Li4EpsFjXepGTo2tYcOTC4a8YsbQXMJ4XUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/concat/-/concat-1.0.3.tgz", + "integrity": "sha512-f/ZaH1aLe64qHgTILdldbvyfGiGF4uzeo9IuXUloIOLQzFmIPloy9QbZadNsuVv0j5qbKQvQb/H/UYf2UsKTpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^2.9.0" + }, + "bin": { + "concat": "bin/concat" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-loader": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.7.tgz", + "integrity": "sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.15", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^3.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.27.0 || ^5.0.0" + } + }, + "node_modules/css-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", + "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^5.2.14", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", + "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-declaration-sorter": "^6.3.1", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.1", + "postcss-convert-values": "^5.1.3", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.4", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.4", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.2", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4", + "npm": ">=1.2" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", + "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.0.1" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/domutils/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.329", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.329.tgz", + "integrity": "sha512-/4t+AS1l4S3ZC0Ja7PHFIWeBIxGA3QGqV8/yKsP36v7NcyUCl+bIcmw6s5zVuMIECWwBrAK/6QLzTmbJChBboQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/file-type": { + "version": "12.4.2", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz", + "integrity": "sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/globby": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", + "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==", + "dev": true, + "license": "MIT" + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/html-loader": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-1.3.2.tgz", + "integrity": "sha512-DEkUwSd0sijK5PF3kRWspYi56XP7bTNkyg5YWSzBdjaSDmvCufep5c4Vpb3PBf6lUL0YPtLwBfy9fL0t5hBAGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "html-minifier-terser": "^5.1.1", + "htmlparser2": "^4.1.0", + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/html-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/html-minifier-terser": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", + "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.1", + "clean-css": "^4.2.3", + "commander": "^4.1.1", + "he": "^1.2.0", + "param-case": "^3.0.3", + "relateurl": "^0.2.7", + "terser": "^4.6.3" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/html-minifier-terser/node_modules/clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/html-minifier-terser/node_modules/terser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", + "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/html-minifier-terser/node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz", + "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imagemin": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-7.0.1.tgz", + "integrity": "sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^12.0.0", + "globby": "^10.0.0", + "graceful-fs": "^4.2.2", + "junk": "^3.1.0", + "make-dir": "^3.0.0", + "p-pipe": "^3.0.0", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/img-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/img-loader/-/img-loader-4.0.0.tgz", + "integrity": "sha512-UwRcPQdwdOyEHyCxe1V9s9YFwInwEWCpoO+kJGfIqDrBDqA8jZUsEZTxQ0JteNPGw/Gupmwesk2OhLTcnw6tnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "imagemin": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/img-loader/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/img-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/immutable": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ipaddr.js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", + "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jquery-ui": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.14.2.tgz", + "integrity": "sha512-1gSl7PUjyipa2adSr780Ujk16faicrV7PjPPzPtvWk7tTqBnsqp67NNV9jZK2+BIxUPXWSnIUU/LBCgwgGZE+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "jquery": ">=1.12.0 <5.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/junk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", + "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/laravel-mix": { + "version": "6.0.49", + "resolved": "https://registry.npmjs.org/laravel-mix/-/laravel-mix-6.0.49.tgz", + "integrity": "sha512-bBMFpFjp26XfijPvY5y9zGKud7VqlyOE0OWUcPo3vTBY5asw8LTjafAbee1dhfLz6PWNqDziz69CP78ELSpfKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.15.8", + "@babel/plugin-proposal-object-rest-spread": "^7.15.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.15.8", + "@babel/preset-env": "^7.15.8", + "@babel/runtime": "^7.15.4", + "@types/babel__core": "^7.1.16", + "@types/clean-css": "^4.2.5", + "@types/imagemin-gifsicle": "^7.0.1", + "@types/imagemin-mozjpeg": "^8.0.1", + "@types/imagemin-optipng": "^5.2.1", + "@types/imagemin-svgo": "^8.0.0", + "autoprefixer": "^10.4.0", + "babel-loader": "^8.2.3", + "chalk": "^4.1.2", + "chokidar": "^3.5.2", + "clean-css": "^5.2.4", + "cli-table3": "^0.6.0", + "collect.js": "^4.28.5", + "commander": "^7.2.0", + "concat": "^1.0.3", + "css-loader": "^5.2.6", + "cssnano": "^5.0.8", + "dotenv": "^10.0.0", + "dotenv-expand": "^5.1.0", + "file-loader": "^6.2.0", + "fs-extra": "^10.0.0", + "glob": "^7.2.0", + "html-loader": "^1.3.2", + "imagemin": "^7.0.1", + "img-loader": "^4.0.0", + "lodash": "^4.17.21", + "md5": "^2.3.0", + "mini-css-extract-plugin": "^1.6.2", + "node-libs-browser": "^2.2.1", + "postcss-load-config": "^3.1.0", + "postcss-loader": "^6.2.0", + "semver": "^7.3.5", + "strip-ansi": "^6.0.0", + "style-loader": "^2.0.0", + "terser": "^5.9.0", + "terser-webpack-plugin": "^5.2.4", + "vue-style-loader": "^4.1.3", + "webpack": "^5.60.0", + "webpack-cli": "^4.9.1", + "webpack-dev-server": "^4.7.3", + "webpack-merge": "^5.8.0", + "webpack-notifier": "^1.14.1", + "webpackbar": "^5.0.0-3", + "yargs": "^17.2.1" + }, + "bin": { + "laravel-mix": "bin/cli.js", + "mix": "bin/cli.js" + }, + "engines": { + "node": ">=12.14.0" + }, + "peerDependencies": { + "@babel/core": "^7.15.8", + "@babel/plugin-proposal-object-rest-spread": "^7.15.6", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.15.8", + "@babel/preset-env": "^7.15.8", + "postcss": "^8.3.11", + "webpack": "^5.60.0", + "webpack-cli": "^4.9.1" + } + }, + "node_modules/launch-editor": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz", + "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz", + "integrity": "sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0", + "webpack-sources": "^1.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.4.0 || ^5.0.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + } + }, + "node_modules/node-notifier": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-9.0.1.tgz", + "integrity": "sha512-fPNFIp2hF/Dq7qLDzSg4vZ0J4e9v60gJR+Qx7RbjbWqzPDdEqeVpEx5CFeDAELIl+A/woaaNn1fQ5nEVerMxJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "growly": "^1.3.0", + "is-wsl": "^2.2.0", + "semver": "^7.3.2", + "shellwords": "^0.1.1", + "uuid": "^8.3.0", + "which": "^2.0.2" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-pipe": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-pipe/-/p-pipe-3.1.0.tgz", + "integrity": "sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.9.tgz", + "integrity": "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "pbkdf2": "^3.1.5", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.5.tgz", + "integrity": "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", + "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", + "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "dev": true, + "license": "MIT", + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", + "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pretty-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", + "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regex-parser": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.1.tgz", + "integrity": "sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-url-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", + "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.14", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/resolve-url-loader/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ripemd160/node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sass": { + "version": "1.98.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.98.0.tgz", + "integrity": "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/sass-loader": { + "version": "16.0.7", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.7.tgz", + "integrity": "sha512-w6q+fRHourZ+e+xA1kcsF27iGM6jdB8teexYCfdUw0sYgcDNeZESnDNT9sUmmPm3ooziwUJXGwZJSTF3kOdBfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/sass/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sass/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.2.tgz", + "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.8.0", + "mime-types": "~2.1.35", + "parseurl": "~1.3.3" + }, + "engines": { + "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "dev": true, + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true, + "license": "MIT" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/spdy-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/style-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", + "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/style-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/stylehacks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svgo": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.2.tgz", + "integrity": "sha512-TyzE4NVGLUFy+H/Uy4N6c3G0HEeprsVfge6Lmq+0FdQQ/zqoVYB62IsBZORsiL+o96s6ff/V6/3UQo/C0cgCAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "sax": "^1.5.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tapable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", + "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue-style-loader": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", + "integrity": "sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-sum": "^1.0.2", + "loader-utils": "^1.0.2" + } + }, + "node_modules/vue-style-loader/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/vue-style-loader/node_modules/loader-utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webpack": { + "version": "5.105.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", + "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", + "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.2.0", + "@webpack-cli/info": "^1.5.0", + "@webpack-cli/serve": "^1.7.0", + "colorette": "^2.0.14", + "commander": "^7.0.0", + "cross-spawn": "^7.0.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "@webpack-cli/migrate": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-dev-middleware/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.4", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack-dev-server/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack-dev-server/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-notifier": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/webpack-notifier/-/webpack-notifier-1.15.0.tgz", + "integrity": "sha512-N2V8UMgRB5komdXQRavBsRpw0hPhJq2/SWNOGuhrXpIgRhcMexzkGQysUyGStHLV5hkUlgpRiF7IUXoBqyMmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "node-notifier": "^9.0.0", + "strip-ansi": "^6.0.0" + }, + "peerDependencies": { + "@types/webpack": ">4.41.31" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack/node_modules/webpack-sources": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpackbar": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.2.tgz", + "integrity": "sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "consola": "^2.15.3", + "pretty-time": "^1.1.0", + "std-env": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "webpack": "3 || 4 || 5" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json index 7d8d40815..a9fd37a78 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "private": true, "scripts": { - "dev": "yarn development", + "dev": "npm run development", "development": "mix", "watch": "mix watch", "watch-poll": "mix watch -- --watch-options-poll=1000", "hot": "mix watch --hot", - "prod": "yarn production", + "prod": "npm run production", "production": "mix --production" }, "devDependencies": { @@ -23,6 +23,5 @@ "ie 11", "last 3 versions" ], - "packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610", "dependencies": {} } diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 46fcde3dd..000000000 --- a/yarn.lock +++ /dev/null @@ -1,5475 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" - integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== - dependencies: - "@babel/helper-validator-identifier" "^7.27.1" - js-tokens "^4.0.0" - picocolors "^1.1.1" - -"@babel/compat-data@^7.20.5", "@babel/compat-data@^7.27.2", "@babel/compat-data@^7.27.7", "@babel/compat-data@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.5.tgz#a8a4962e1567121ac0b3b487f52107443b455c7f" - integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA== - -"@babel/core@^7.15.8": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.5.tgz#4c81b35e51e1b734f510c99b07dfbc7bbbb48f7e" - integrity sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.5" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-module-transforms" "^7.28.3" - "@babel/helpers" "^7.28.4" - "@babel/parser" "^7.28.5" - "@babel/template" "^7.27.2" - "@babel/traverse" "^7.28.5" - "@babel/types" "^7.28.5" - "@jridgewell/remapping" "^2.3.5" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - -"@babel/generator@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.5.tgz#712722d5e50f44d07bc7ac9fe84438742dd61298" - integrity sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ== - dependencies: - "@babel/parser" "^7.28.5" - "@babel/types" "^7.28.5" - "@jridgewell/gen-mapping" "^0.3.12" - "@jridgewell/trace-mapping" "^0.3.28" - jsesc "^3.0.2" - -"@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3": - version "7.27.3" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz#f31fd86b915fc4daf1f3ac6976c59be7084ed9c5" - integrity sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg== - dependencies: - "@babel/types" "^7.27.3" - -"@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" - integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== - dependencies: - "@babel/compat-data" "^7.27.2" - "@babel/helper-validator-option" "^7.27.1" - browserslist "^4.24.0" - lru-cache "^5.1.1" - semver "^6.3.1" - -"@babel/helper-create-class-features-plugin@^7.27.1", "@babel/helper-create-class-features-plugin@^7.28.3": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz#472d0c28028850968979ad89f173594a6995da46" - integrity sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.27.3" - "@babel/helper-member-expression-to-functions" "^7.28.5" - "@babel/helper-optimise-call-expression" "^7.27.1" - "@babel/helper-replace-supers" "^7.27.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - "@babel/traverse" "^7.28.5" - semver "^6.3.1" - -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.27.1": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz#7c1ddd64b2065c7f78034b25b43346a7e19ed997" - integrity sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.27.3" - regexpu-core "^6.3.1" - semver "^6.3.1" - -"@babel/helper-define-polyfill-provider@^0.6.5": - version "0.6.5" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz#742ccf1cb003c07b48859fc9fa2c1bbe40e5f753" - integrity sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg== - dependencies: - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" - debug "^4.4.1" - lodash.debounce "^4.0.8" - resolve "^1.22.10" - -"@babel/helper-globals@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" - integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== - -"@babel/helper-member-expression-to-functions@^7.27.1", "@babel/helper-member-expression-to-functions@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz#f3e07a10be37ed7a63461c63e6929575945a6150" - integrity sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg== - dependencies: - "@babel/traverse" "^7.28.5" - "@babel/types" "^7.28.5" - -"@babel/helper-module-imports@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" - integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== - dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" - integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== - dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-validator-identifier" "^7.27.1" - "@babel/traverse" "^7.28.3" - -"@babel/helper-optimise-call-expression@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz#c65221b61a643f3e62705e5dd2b5f115e35f9200" - integrity sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw== - dependencies: - "@babel/types" "^7.27.1" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.27.1", "@babel/helper-plugin-utils@^7.8.0": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" - integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== - -"@babel/helper-remap-async-to-generator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz#4601d5c7ce2eb2aea58328d43725523fcd362ce6" - integrity sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - "@babel/helper-wrap-function" "^7.27.1" - "@babel/traverse" "^7.27.1" - -"@babel/helper-replace-supers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz#b1ed2d634ce3bdb730e4b52de30f8cccfd692bc0" - integrity sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.27.1" - "@babel/helper-optimise-call-expression" "^7.27.1" - "@babel/traverse" "^7.27.1" - -"@babel/helper-skip-transparent-expression-wrappers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz#62bb91b3abba8c7f1fec0252d9dbea11b3ee7a56" - integrity sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg== - dependencies: - "@babel/traverse" "^7.27.1" - "@babel/types" "^7.27.1" - -"@babel/helper-string-parser@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" - integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== - -"@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" - integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== - -"@babel/helper-validator-option@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" - integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== - -"@babel/helper-wrap-function@^7.27.1": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz#fe4872092bc1438ffd0ce579e6f699609f9d0a7a" - integrity sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g== - dependencies: - "@babel/template" "^7.27.2" - "@babel/traverse" "^7.28.3" - "@babel/types" "^7.28.2" - -"@babel/helpers@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" - integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== - dependencies: - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.4" - -"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.27.2", "@babel/parser@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.5.tgz#0b0225ee90362f030efd644e8034c99468893b08" - integrity sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ== - dependencies: - "@babel/types" "^7.28.5" - -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz#fbde57974707bbfa0376d34d425ff4fa6c732421" - integrity sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/traverse" "^7.28.5" - -"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz#43f70a6d7efd52370eefbdf55ae03d91b293856d" - integrity sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz#beb623bd573b8b6f3047bd04c32506adc3e58a72" - integrity sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz#e134a5479eb2ba9c02714e8c1ebf1ec9076124fd" - integrity sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - "@babel/plugin-transform-optional-chaining" "^7.27.1" - -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz#373f6e2de0016f73caf8f27004f61d167743742a" - integrity sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/traverse" "^7.28.3" - -"@babel/plugin-proposal-object-rest-spread@^7.15.6": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" - integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== - dependencies: - "@babel/compat-data" "^7.20.5" - "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.20.7" - -"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": - version "7.21.0-placeholder-for-preset-env.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" - integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== - -"@babel/plugin-syntax-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" - integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-import-assertions@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz#88894aefd2b03b5ee6ad1562a7c8e1587496aecd" - integrity sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-import-attributes@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz#34c017d54496f9b11b61474e7ea3dfd5563ffe07" - integrity sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" - integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-arrow-functions@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz#6e2061067ba3ab0266d834a9f94811196f2aba9a" - integrity sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-async-generator-functions@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz#1276e6c7285ab2cd1eccb0bc7356b7a69ff842c2" - integrity sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-remap-async-to-generator" "^7.27.1" - "@babel/traverse" "^7.28.0" - -"@babel/plugin-transform-async-to-generator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz#9a93893b9379b39466c74474f55af03de78c66e7" - integrity sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA== - dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-remap-async-to-generator" "^7.27.1" - -"@babel/plugin-transform-block-scoped-functions@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz#558a9d6e24cf72802dd3b62a4b51e0d62c0f57f9" - integrity sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-block-scoping@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz#e0d3af63bd8c80de2e567e690a54e84d85eb16f6" - integrity sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-class-properties@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz#dd40a6a370dfd49d32362ae206ddaf2bb082a925" - integrity sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-class-static-block@^7.28.3": - version "7.28.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz#d1b8e69b54c9993bc558203e1f49bfc979bfd852" - integrity sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.28.3" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-classes@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz#75d66175486788c56728a73424d67cbc7473495c" - integrity sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.27.3" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-globals" "^7.28.0" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-replace-supers" "^7.27.1" - "@babel/traverse" "^7.28.4" - -"@babel/plugin-transform-computed-properties@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz#81662e78bf5e734a97982c2b7f0a793288ef3caa" - integrity sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/template" "^7.27.1" - -"@babel/plugin-transform-destructuring@^7.28.0", "@babel/plugin-transform-destructuring@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz#b8402764df96179a2070bb7b501a1586cf8ad7a7" - integrity sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/traverse" "^7.28.5" - -"@babel/plugin-transform-dotall-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz#aa6821de864c528b1fecf286f0a174e38e826f4d" - integrity sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-duplicate-keys@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz#f1fbf628ece18e12e7b32b175940e68358f546d1" - integrity sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz#5043854ca620a94149372e69030ff8cb6a9eb0ec" - integrity sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-dynamic-import@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz#4c78f35552ac0e06aa1f6e3c573d67695e8af5a4" - integrity sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-explicit-resource-management@^7.28.0": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz#45be6211b778dbf4b9d54c4e8a2b42fa72e09a1a" - integrity sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-transform-destructuring" "^7.28.0" - -"@babel/plugin-transform-exponentiation-operator@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz#7cc90a8170e83532676cfa505278e147056e94fe" - integrity sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-export-namespace-from@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz#71ca69d3471edd6daa711cf4dfc3400415df9c23" - integrity sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-for-of@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz#bc24f7080e9ff721b63a70ac7b2564ca15b6c40a" - integrity sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - -"@babel/plugin-transform-function-name@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz#4d0bf307720e4dce6d7c30fcb1fd6ca77bdeb3a7" - integrity sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ== - dependencies: - "@babel/helper-compilation-targets" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/traverse" "^7.27.1" - -"@babel/plugin-transform-json-strings@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz#a2e0ce6ef256376bd527f290da023983527a4f4c" - integrity sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-literals@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz#baaefa4d10a1d4206f9dcdda50d7d5827bb70b24" - integrity sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-logical-assignment-operators@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz#d028fd6db8c081dee4abebc812c2325e24a85b0e" - integrity sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-member-expression-literals@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz#37b88ba594d852418e99536f5612f795f23aeaf9" - integrity sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-modules-amd@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz#a4145f9d87c2291fe2d05f994b65dba4e3e7196f" - integrity sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA== - dependencies: - "@babel/helper-module-transforms" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-modules-commonjs@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz#8e44ed37c2787ecc23bdc367f49977476614e832" - integrity sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw== - dependencies: - "@babel/helper-module-transforms" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-modules-systemjs@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz#7439e592a92d7670dfcb95d0cbc04bd3e64801d2" - integrity sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew== - dependencies: - "@babel/helper-module-transforms" "^7.28.3" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-validator-identifier" "^7.28.5" - "@babel/traverse" "^7.28.5" - -"@babel/plugin-transform-modules-umd@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz#63f2cf4f6dc15debc12f694e44714863d34cd334" - integrity sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w== - dependencies: - "@babel/helper-module-transforms" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz#f32b8f7818d8fc0cc46ee20a8ef75f071af976e1" - integrity sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-new-target@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz#259c43939728cad1706ac17351b7e6a7bea1abeb" - integrity sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-nullish-coalescing-operator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz#4f9d3153bf6782d73dd42785a9d22d03197bc91d" - integrity sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-numeric-separator@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz#614e0b15cc800e5997dadd9bd6ea524ed6c819c6" - integrity sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-object-rest-spread@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz#9ee1ceca80b3e6c4bac9247b2149e36958f7f98d" - integrity sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew== - dependencies: - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/plugin-transform-destructuring" "^7.28.0" - "@babel/plugin-transform-parameters" "^7.27.7" - "@babel/traverse" "^7.28.4" - -"@babel/plugin-transform-object-super@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz#1c932cd27bf3874c43a5cac4f43ebf970c9871b5" - integrity sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-replace-supers" "^7.27.1" - -"@babel/plugin-transform-optional-catch-binding@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz#84c7341ebde35ccd36b137e9e45866825072a30c" - integrity sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-optional-chaining@^7.27.1", "@babel/plugin-transform-optional-chaining@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz#8238c785f9d5c1c515a90bf196efb50d075a4b26" - integrity sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - -"@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.27.7": - version "7.27.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz#1fd2febb7c74e7d21cf3b05f7aebc907940af53a" - integrity sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-private-methods@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz#fdacbab1c5ed81ec70dfdbb8b213d65da148b6af" - integrity sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-private-property-in-object@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz#4dbbef283b5b2f01a21e81e299f76e35f900fb11" - integrity sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.27.1" - "@babel/helper-create-class-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-property-literals@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz#07eafd618800591e88073a0af1b940d9a42c6424" - integrity sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-regenerator@^7.28.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz#9d3fa3bebb48ddd0091ce5729139cd99c67cea51" - integrity sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-regexp-modifiers@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz#df9ba5577c974e3f1449888b70b76169998a6d09" - integrity sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-reserved-words@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz#40fba4878ccbd1c56605a4479a3a891ac0274bb4" - integrity sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-runtime@^7.15.8": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz#ae3e21fbefe2831ebac04dfa6b463691696afe17" - integrity sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w== - dependencies: - "@babel/helper-module-imports" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - babel-plugin-polyfill-corejs2 "^0.4.14" - babel-plugin-polyfill-corejs3 "^0.13.0" - babel-plugin-polyfill-regenerator "^0.6.5" - semver "^6.3.1" - -"@babel/plugin-transform-shorthand-properties@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz#532abdacdec87bfee1e0ef8e2fcdee543fe32b90" - integrity sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-spread@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz#1a264d5fc12750918f50e3fe3e24e437178abb08" - integrity sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.27.1" - -"@babel/plugin-transform-sticky-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz#18984935d9d2296843a491d78a014939f7dcd280" - integrity sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-template-literals@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz#1a0eb35d8bb3e6efc06c9fd40eb0bcef548328b8" - integrity sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-typeof-symbol@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz#70e966bb492e03509cf37eafa6dcc3051f844369" - integrity sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-unicode-escapes@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz#3e3143f8438aef842de28816ece58780190cf806" - integrity sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg== - dependencies: - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-unicode-property-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz#bdfe2d3170c78c5691a3c3be934c8c0087525956" - integrity sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-unicode-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz#25948f5c395db15f609028e370667ed8bae9af97" - integrity sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/plugin-transform-unicode-sets-regex@^7.27.1": - version "7.27.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz#6ab706d10f801b5c72da8bb2548561fa04193cd1" - integrity sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.27.1" - "@babel/helper-plugin-utils" "^7.27.1" - -"@babel/preset-env@^7.15.8": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.28.5.tgz#82dd159d1563f219a1ce94324b3071eb89e280b0" - integrity sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg== - dependencies: - "@babel/compat-data" "^7.28.5" - "@babel/helper-compilation-targets" "^7.27.2" - "@babel/helper-plugin-utils" "^7.27.1" - "@babel/helper-validator-option" "^7.27.1" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.28.5" - "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.27.1" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.27.1" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.27.1" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.28.3" - "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-import-assertions" "^7.27.1" - "@babel/plugin-syntax-import-attributes" "^7.27.1" - "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.27.1" - "@babel/plugin-transform-async-generator-functions" "^7.28.0" - "@babel/plugin-transform-async-to-generator" "^7.27.1" - "@babel/plugin-transform-block-scoped-functions" "^7.27.1" - "@babel/plugin-transform-block-scoping" "^7.28.5" - "@babel/plugin-transform-class-properties" "^7.27.1" - "@babel/plugin-transform-class-static-block" "^7.28.3" - "@babel/plugin-transform-classes" "^7.28.4" - "@babel/plugin-transform-computed-properties" "^7.27.1" - "@babel/plugin-transform-destructuring" "^7.28.5" - "@babel/plugin-transform-dotall-regex" "^7.27.1" - "@babel/plugin-transform-duplicate-keys" "^7.27.1" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.27.1" - "@babel/plugin-transform-dynamic-import" "^7.27.1" - "@babel/plugin-transform-explicit-resource-management" "^7.28.0" - "@babel/plugin-transform-exponentiation-operator" "^7.28.5" - "@babel/plugin-transform-export-namespace-from" "^7.27.1" - "@babel/plugin-transform-for-of" "^7.27.1" - "@babel/plugin-transform-function-name" "^7.27.1" - "@babel/plugin-transform-json-strings" "^7.27.1" - "@babel/plugin-transform-literals" "^7.27.1" - "@babel/plugin-transform-logical-assignment-operators" "^7.28.5" - "@babel/plugin-transform-member-expression-literals" "^7.27.1" - "@babel/plugin-transform-modules-amd" "^7.27.1" - "@babel/plugin-transform-modules-commonjs" "^7.27.1" - "@babel/plugin-transform-modules-systemjs" "^7.28.5" - "@babel/plugin-transform-modules-umd" "^7.27.1" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.27.1" - "@babel/plugin-transform-new-target" "^7.27.1" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.27.1" - "@babel/plugin-transform-numeric-separator" "^7.27.1" - "@babel/plugin-transform-object-rest-spread" "^7.28.4" - "@babel/plugin-transform-object-super" "^7.27.1" - "@babel/plugin-transform-optional-catch-binding" "^7.27.1" - "@babel/plugin-transform-optional-chaining" "^7.28.5" - "@babel/plugin-transform-parameters" "^7.27.7" - "@babel/plugin-transform-private-methods" "^7.27.1" - "@babel/plugin-transform-private-property-in-object" "^7.27.1" - "@babel/plugin-transform-property-literals" "^7.27.1" - "@babel/plugin-transform-regenerator" "^7.28.4" - "@babel/plugin-transform-regexp-modifiers" "^7.27.1" - "@babel/plugin-transform-reserved-words" "^7.27.1" - "@babel/plugin-transform-shorthand-properties" "^7.27.1" - "@babel/plugin-transform-spread" "^7.27.1" - "@babel/plugin-transform-sticky-regex" "^7.27.1" - "@babel/plugin-transform-template-literals" "^7.27.1" - "@babel/plugin-transform-typeof-symbol" "^7.27.1" - "@babel/plugin-transform-unicode-escapes" "^7.27.1" - "@babel/plugin-transform-unicode-property-regex" "^7.27.1" - "@babel/plugin-transform-unicode-regex" "^7.27.1" - "@babel/plugin-transform-unicode-sets-regex" "^7.27.1" - "@babel/preset-modules" "0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2 "^0.4.14" - babel-plugin-polyfill-corejs3 "^0.13.0" - babel-plugin-polyfill-regenerator "^0.6.5" - core-js-compat "^3.43.0" - semver "^6.3.1" - -"@babel/preset-modules@0.1.6-no-external-plugins": - version "0.1.6-no-external-plugins" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" - integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - -"@babel/runtime@^7.15.4": - version "7.28.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" - integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== - -"@babel/template@^7.27.1", "@babel/template@^7.27.2": - version "7.27.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" - integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/parser" "^7.27.2" - "@babel/types" "^7.27.1" - -"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4", "@babel/traverse@^7.28.5": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.5.tgz#450cab9135d21a7a2ca9d2d35aa05c20e68c360b" - integrity sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ== - dependencies: - "@babel/code-frame" "^7.27.1" - "@babel/generator" "^7.28.5" - "@babel/helper-globals" "^7.28.0" - "@babel/parser" "^7.28.5" - "@babel/template" "^7.27.2" - "@babel/types" "^7.28.5" - debug "^4.3.1" - -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.28.5", "@babel/types@^7.4.4": - version "7.28.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.5.tgz#10fc405f60897c35f07e85493c932c7b5ca0592b" - integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA== - dependencies: - "@babel/helper-string-parser" "^7.27.1" - "@babel/helper-validator-identifier" "^7.28.5" - -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@discoveryjs/json-ext@^0.5.0": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" - integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== - -"@isaacs/balanced-match@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" - integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== - -"@isaacs/brace-expansion@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" - integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== - dependencies: - "@isaacs/balanced-match" "^4.0.1" - -"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": - version "0.3.13" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" - integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/remapping@^2.3.5": - version "2.3.5" - resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" - integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/source-map@^0.3.3": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.11.tgz#b21835cbd36db656b857c2ad02ebd413cc13a9ba" - integrity sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - -"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" - integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": - version "0.3.31" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" - integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@leichtgewicht/ip-codec@^2.0.1": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" - integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@parcel/watcher-android-arm64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz#507f836d7e2042f798c7d07ad19c3546f9848ac1" - integrity sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA== - -"@parcel/watcher-darwin-arm64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz#3d26dce38de6590ef79c47ec2c55793c06ad4f67" - integrity sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw== - -"@parcel/watcher-darwin-x64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz#99f3af3869069ccf774e4ddfccf7e64fd2311ef8" - integrity sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg== - -"@parcel/watcher-freebsd-x64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz#14d6857741a9f51dfe51d5b08b7c8afdbc73ad9b" - integrity sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ== - -"@parcel/watcher-linux-arm-glibc@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz#43c3246d6892381db473bb4f663229ad20b609a1" - integrity sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA== - -"@parcel/watcher-linux-arm-musl@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz#663750f7090bb6278d2210de643eb8a3f780d08e" - integrity sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q== - -"@parcel/watcher-linux-arm64-glibc@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz#ba60e1f56977f7e47cd7e31ad65d15fdcbd07e30" - integrity sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w== - -"@parcel/watcher-linux-arm64-musl@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz#f7fbcdff2f04c526f96eac01f97419a6a99855d2" - integrity sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg== - -"@parcel/watcher-linux-x64-glibc@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz#4d2ea0f633eb1917d83d483392ce6181b6a92e4e" - integrity sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A== - -"@parcel/watcher-linux-x64-musl@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz#277b346b05db54f55657301dd77bdf99d63606ee" - integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg== - -"@parcel/watcher-win32-arm64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz#7e9e02a26784d47503de1d10e8eab6cceb524243" - integrity sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw== - -"@parcel/watcher-win32-ia32@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz#2d0f94fa59a873cdc584bf7f6b1dc628ddf976e6" - integrity sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ== - -"@parcel/watcher-win32-x64@2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz#ae52693259664ba6f2228fa61d7ee44b64ea0947" - integrity sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA== - -"@parcel/watcher@^2.4.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.1.tgz#342507a9cfaaf172479a882309def1e991fb1200" - integrity sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg== - dependencies: - detect-libc "^1.0.3" - is-glob "^4.0.3" - micromatch "^4.0.5" - node-addon-api "^7.0.0" - optionalDependencies: - "@parcel/watcher-android-arm64" "2.5.1" - "@parcel/watcher-darwin-arm64" "2.5.1" - "@parcel/watcher-darwin-x64" "2.5.1" - "@parcel/watcher-freebsd-x64" "2.5.1" - "@parcel/watcher-linux-arm-glibc" "2.5.1" - "@parcel/watcher-linux-arm-musl" "2.5.1" - "@parcel/watcher-linux-arm64-glibc" "2.5.1" - "@parcel/watcher-linux-arm64-musl" "2.5.1" - "@parcel/watcher-linux-x64-glibc" "2.5.1" - "@parcel/watcher-linux-x64-musl" "2.5.1" - "@parcel/watcher-win32-arm64" "2.5.1" - "@parcel/watcher-win32-ia32" "2.5.1" - "@parcel/watcher-win32-x64" "2.5.1" - -"@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== - -"@types/babel__core@^7.1.16": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.27.0" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" - integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" - integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" - integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== - dependencies: - "@babel/types" "^7.28.2" - -"@types/body-parser@*": - version "1.19.6" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" - integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/bonjour@^3.5.9": - version "3.5.13" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" - integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== - dependencies: - "@types/node" "*" - -"@types/clean-css@^4.2.5": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@types/clean-css/-/clean-css-4.2.11.tgz#3f170dedd8d096fe7e7bd1c8dda0c8314217cbe6" - integrity sha512-Y8n81lQVTAfP2TOdtJJEsCoYl1AnOkqDqMvXb9/7pfgZZ7r8YrEyurrAvAoAjHOGXKRybay+5CsExqIH6liccw== - dependencies: - "@types/node" "*" - source-map "^0.6.0" - -"@types/connect-history-api-fallback@^1.3.5": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" - integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== - dependencies: - "@types/express-serve-static-core" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.38" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - -"@types/eslint-scope@^3.7.7": - version "3.7.7" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" - integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "9.6.1" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.1.tgz#d5795ad732ce81715f27f75da913004a56751584" - integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*", "@types/estree@^1.0.8": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" - integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== - -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz#74f47555b3d804b54cb7030e6f9aa0c7485cfc5b" - integrity sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express-serve-static-core@^4.17.33": - version "4.19.7" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz#f1d306dcc03b1aafbfb6b4fe684cce8a31cffc10" - integrity sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@*": - version "5.0.6" - resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.6.tgz#2d724b2c990dcb8c8444063f3580a903f6d500cc" - integrity sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^5.0.0" - "@types/serve-static" "^2" - -"@types/express@^4.17.13": - version "4.17.25" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.25.tgz#070c8c73a6fee6936d65c195dbbfb7da5026649b" - integrity sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "^1" - -"@types/glob@^7.1.1": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" - integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - -"@types/http-errors@*": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" - integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== - -"@types/http-proxy@^1.17.8": - version "1.17.17" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.17.tgz#d9e2c4571fe3507343cb210cd41790375e59a533" - integrity sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw== - dependencies: - "@types/node" "*" - -"@types/imagemin-gifsicle@^7.0.1": - version "7.0.4" - resolved "https://registry.yarnpkg.com/@types/imagemin-gifsicle/-/imagemin-gifsicle-7.0.4.tgz#59522cf8115a74bd2547a815b2de7535dd10afde" - integrity sha512-ZghMBd/Jgqg5utTJNPmvf6DkuHzMhscJ8vgf/7MUGCpO+G+cLrhYltL+5d+h3A1B4W73S2SrmJZ1jS5LACpX+A== - dependencies: - "@types/imagemin" "*" - -"@types/imagemin-mozjpeg@^8.0.1": - version "8.0.4" - resolved "https://registry.yarnpkg.com/@types/imagemin-mozjpeg/-/imagemin-mozjpeg-8.0.4.tgz#e419c277d30501eec8b031aa776452e4817d89b7" - integrity sha512-ZCAxV8SYJB8ehwHpnbRpHjg5Wc4HcyuAMiDhXbkgC7gujDoOTyHO3dhDkUtZ1oK1DLBRZapqG9etdLVhUml7yQ== - dependencies: - "@types/imagemin" "*" - -"@types/imagemin-optipng@^5.2.1": - version "5.2.4" - resolved "https://registry.yarnpkg.com/@types/imagemin-optipng/-/imagemin-optipng-5.2.4.tgz#52a5c2d6d7a4a1377d8ccd147adff4f82664792f" - integrity sha512-mvKnDMC8eCYZetAQudjs1DbgpR84WhsTx1wgvdiXnpuUEti3oJ+MaMYBRWPY0JlQ4+y4TXKOfa7+LOuT8daegQ== - dependencies: - "@types/imagemin" "*" - -"@types/imagemin-svgo@^8.0.0": - version "8.0.1" - resolved "https://registry.yarnpkg.com/@types/imagemin-svgo/-/imagemin-svgo-8.0.1.tgz#03af689b75dbdeb634c2457ba22043530a00d87e" - integrity sha512-YafkdrVAcr38U0Ln1C+L1n4SIZqC47VBHTyxCq7gTUSd1R9MdIvMcrljWlgU1M9O68WZDeQWUrKipKYfEOCOvQ== - dependencies: - "@types/imagemin" "*" - "@types/svgo" "^1" - -"@types/imagemin@*": - version "9.0.1" - resolved "https://registry.yarnpkg.com/@types/imagemin/-/imagemin-9.0.1.tgz#94293f66b5a0d34ce307d47dc3f6e6bae578fafa" - integrity sha512-xMWpvrUhtYxl6EeW+UhVH3rwUKhCRx21XddcoWByjDAasXZT5pQaCn0YVnXoTijX5hlTrGqV4TGQL/Htpp00+w== - dependencies: - "@types/node" "*" - -"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== - -"@types/minimatch@*": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-6.0.0.tgz#4d207b1cc941367bdcd195a3a781a7e4fc3b1e03" - integrity sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA== - dependencies: - minimatch "*" - -"@types/node-forge@^1.3.0": - version "1.3.14" - resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.14.tgz#006c2616ccd65550560c2757d8472eb6d3ecea0b" - integrity sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw== - dependencies: - "@types/node" "*" - -"@types/node@*": - version "25.0.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.2.tgz#411f9dd6cb2bf5ee46aed7199a9f85ca6b068b4e" - integrity sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA== - dependencies: - undici-types "~7.16.0" - -"@types/parse-json@^4.0.0": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" - integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== - -"@types/qs@*": - version "6.14.0" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" - integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== - -"@types/range-parser@*": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" - integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== - -"@types/retry@0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" - integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== - -"@types/send@*": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.1.tgz#6a784e45543c18c774c049bff6d3dbaf045c9c74" - integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== - dependencies: - "@types/node" "*" - -"@types/send@<1": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.6.tgz#aeb5385be62ff58a52cd5459daa509ae91651d25" - integrity sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-index@^1.9.1": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" - integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== - dependencies: - "@types/express" "*" - -"@types/serve-static@^1", "@types/serve-static@^1.13.10": - version "1.15.10" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.10.tgz#768169145a778f8f5dfcb6360aead414a3994fee" - integrity sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw== - dependencies: - "@types/http-errors" "*" - "@types/node" "*" - "@types/send" "<1" - -"@types/serve-static@^2": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-2.2.0.tgz#d4a447503ead0d1671132d1ab6bd58b805d8de6a" - integrity sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== - dependencies: - "@types/http-errors" "*" - "@types/node" "*" - -"@types/sockjs@^0.3.33": - version "0.3.36" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" - integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== - dependencies: - "@types/node" "*" - -"@types/svgo@^1": - version "1.3.6" - resolved "https://registry.yarnpkg.com/@types/svgo/-/svgo-1.3.6.tgz#9db00a7ddf9b26ad2feb6b834bef1818677845e1" - integrity sha512-AZU7vQcy/4WFEuwnwsNsJnFwupIpbllH1++LXScN6uxT1Z4zPzdrWG97w4/I7eFKFTvfy/bHFStWjdBAg2Vjug== - -"@types/ws@^8.5.5": - version "8.18.1" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9" - integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== - dependencies: - "@types/node" "*" - -"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" - integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== - dependencies: - "@webassemblyjs/helper-numbers" "1.13.2" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - -"@webassemblyjs/floating-point-hex-parser@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" - integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== - -"@webassemblyjs/helper-api-error@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" - integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== - -"@webassemblyjs/helper-buffer@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" - integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== - -"@webassemblyjs/helper-numbers@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" - integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.13.2" - "@webassemblyjs/helper-api-error" "1.13.2" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" - integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== - -"@webassemblyjs/helper-wasm-section@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" - integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/wasm-gen" "1.14.1" - -"@webassemblyjs/ieee754@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" - integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" - integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" - integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== - -"@webassemblyjs/wasm-edit@^1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" - integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/helper-wasm-section" "1.14.1" - "@webassemblyjs/wasm-gen" "1.14.1" - "@webassemblyjs/wasm-opt" "1.14.1" - "@webassemblyjs/wasm-parser" "1.14.1" - "@webassemblyjs/wast-printer" "1.14.1" - -"@webassemblyjs/wasm-gen@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" - integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/ieee754" "1.13.2" - "@webassemblyjs/leb128" "1.13.2" - "@webassemblyjs/utf8" "1.13.2" - -"@webassemblyjs/wasm-opt@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" - integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/wasm-gen" "1.14.1" - "@webassemblyjs/wasm-parser" "1.14.1" - -"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" - integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-api-error" "1.13.2" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/ieee754" "1.13.2" - "@webassemblyjs/leb128" "1.13.2" - "@webassemblyjs/utf8" "1.13.2" - -"@webassemblyjs/wast-printer@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" - integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@xtuc/long" "4.2.2" - -"@webpack-cli/configtest@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz#7b20ce1c12533912c3b217ea68262365fa29a6f5" - integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg== - -"@webpack-cli/info@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.5.0.tgz#6c78c13c5874852d6e2dd17f08a41f3fe4c261b1" - integrity sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ== - dependencies: - envinfo "^7.7.3" - -"@webpack-cli/serve@^1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1" - integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q== - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -accepts@~1.3.4, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-import-phases@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz#16eb850ba99a056cb7cbfe872ffb8972e18c8bd7" - integrity sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ== - -acorn@^8.15.0: - version "8.15.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" - integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== - -adjust-sourcemap-loader@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz#fc4a0fd080f7d10471f30a7320f25560ade28c99" - integrity sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A== - dependencies: - loader-utils "^2.0.0" - regex-parser "^2.2.11" - -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv-keywords@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" - integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== - dependencies: - fast-deep-equal "^3.1.3" - -ajv@^6.12.4, ajv@^6.12.5: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.0.0, ajv@^8.9.0: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== - dependencies: - fast-deep-equal "^3.1.3" - fast-uri "^3.0.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - -ansi-html-community@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -asn1.js@^4.10.1: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -assert@^1.1.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.1.tgz#038ab248e4ff078e7bc2485ba6e6388466c78f76" - integrity sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A== - dependencies: - object.assign "^4.1.4" - util "^0.10.4" - -autoprefixer@^10.4.0: - version "10.4.23" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.23.tgz#c6aa6db8e7376fcd900f9fd79d143ceebad8c4e6" - integrity sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA== - dependencies: - browserslist "^4.28.1" - caniuse-lite "^1.0.30001760" - fraction.js "^5.3.4" - picocolors "^1.1.1" - postcss-value-parser "^4.2.0" - -available-typed-arrays@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" - integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== - dependencies: - possible-typed-array-names "^1.0.0" - -babel-loader@^8.2.3: - version "8.4.1" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.4.1.tgz#6ccb75c66e62c3b144e1c5f2eaec5b8f6c08c675" - integrity sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA== - dependencies: - find-cache-dir "^3.3.1" - loader-utils "^2.0.4" - make-dir "^3.1.0" - schema-utils "^2.6.5" - -babel-plugin-polyfill-corejs2@^0.4.14: - version "0.4.14" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz#8101b82b769c568835611542488d463395c2ef8f" - integrity sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg== - dependencies: - "@babel/compat-data" "^7.27.7" - "@babel/helper-define-polyfill-provider" "^0.6.5" - semver "^6.3.1" - -babel-plugin-polyfill-corejs3@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz#bb7f6aeef7addff17f7602a08a6d19a128c30164" - integrity sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.5" - core-js-compat "^3.43.0" - -babel-plugin-polyfill-regenerator@^0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz#32752e38ab6f6767b92650347bf26a31b16ae8c5" - integrity sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.5" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.0.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -baseline-browser-mapping@^2.9.0: - version "2.9.7" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.7.tgz#d36ce64f2a2c468f6f743c8db495d319120007db" - integrity sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg== - -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: - version "4.12.2" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.2.tgz#3d8fed6796c24e177737f7cc5172ee04ef39ec99" - integrity sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw== - -bn.js@^5.2.1, bn.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.2.tgz#82c09f9ebbb17107cd72cb7fd39bd1f9d0aaa566" - integrity sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw== - -body-parser@~1.20.3: - version "1.20.4" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f" - integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA== - dependencies: - bytes "~3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "~1.2.0" - http-errors "~2.0.1" - iconv-lite "~0.4.24" - on-finished "~2.4.1" - qs "~6.14.0" - raw-body "~2.5.3" - type-is "~1.6.18" - unpipe "~1.0.0" - -bonjour-service@^1.0.11: - version "1.3.0" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.3.0.tgz#80d867430b5a0da64e82a8047fc1e355bdb71722" - integrity sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA== - dependencies: - fast-deep-equal "^3.1.3" - multicast-dns "^7.2.5" - -boolbase@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - -bootstrap-sass@3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/bootstrap-sass/-/bootstrap-sass-3.4.3.tgz#742cc8f4286303ae9fe8e4c95237321eae73766c" - integrity sha512-vPgFnGMp1jWZZupOND65WS6mkR8rxhJxndT/AcMbqcq1hHMdkcH4sMPhznLzzoHOHkSCrd6J9F8pWBriPCKP2Q== - -brace-expansion@^1.1.7: - version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" - integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.3, braces@~3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -brorand@^1.0.1, brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== - -browserify-aes@^1.0.4, browserify-aes@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" - integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -browserify-rsa@^4.0.0, browserify-rsa@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.1.tgz#06e530907fe2949dc21fc3c2e2302e10b1437238" - integrity sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ== - dependencies: - bn.js "^5.2.1" - randombytes "^2.1.0" - safe-buffer "^5.2.1" - -browserify-sign@^4.2.3: - version "4.2.5" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.5.tgz#3979269fa8af55ba18aac35deef11b45515cd27d" - integrity sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw== - dependencies: - bn.js "^5.2.2" - browserify-rsa "^4.1.1" - create-hash "^1.2.0" - create-hmac "^1.1.7" - elliptic "^6.6.1" - inherits "^2.0.4" - parse-asn1 "^5.1.9" - readable-stream "^2.3.8" - safe-buffer "^5.2.1" - -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== - dependencies: - pako "~1.0.5" - -browserslist@^4.0.0, browserslist@^4.21.4, browserslist@^4.24.0, browserslist@^4.26.3, browserslist@^4.28.0, browserslist@^4.28.1: - version "4.28.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" - integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== - dependencies: - baseline-browser-mapping "^2.9.0" - caniuse-lite "^1.0.30001759" - electron-to-chromium "^1.5.263" - node-releases "^2.0.27" - update-browserslist-db "^1.2.0" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== - -buffer@^4.3.0: - version "4.9.2" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" - integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ== - -bytes@3.1.2, bytes@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bind@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" - integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-define-property "^1.0.0" - get-intrinsic "^1.2.4" - set-function-length "^1.2.2" - -call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" - integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - dependencies: - call-bind-apply-helpers "^1.0.2" - get-intrinsic "^1.3.0" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camel-case@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" - integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== - dependencies: - pascal-case "^3.1.2" - tslib "^2.0.3" - -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001759, caniuse-lite@^1.0.30001760: - version "1.0.30001760" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz#bdd1960fafedf8d5f04ff16e81460506ff9b798f" - integrity sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw== - -chalk@^4.1.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -charenc@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" - integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== - -chokidar@^3.5.2, chokidar@^3.5.3: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chokidar@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" - integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== - dependencies: - readdirp "^4.0.1" - -chrome-trace-event@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" - integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.7" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.7.tgz#bd094bfef42634ccfd9e13b9fc73274997111e39" - integrity sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA== - dependencies: - inherits "^2.0.4" - safe-buffer "^5.2.1" - to-buffer "^1.2.2" - -clean-css@^4.2.3: - version "4.2.4" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178" - integrity sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A== - dependencies: - source-map "~0.6.0" - -clean-css@^5.2.4: - version "5.3.3" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd" - integrity sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg== - dependencies: - source-map "~0.6.0" - -cli-table3@^0.6.0: - version "0.6.5" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" - integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== - dependencies: - string-width "^4.2.0" - optionalDependencies: - "@colors/colors" "1.5.0" - -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -collect.js@^4.28.5: - version "4.36.1" - resolved "https://registry.yarnpkg.com/collect.js/-/collect.js-4.36.1.tgz#0194c52e90e01db6f136d28e7a3cf88c5687894d" - integrity sha512-jd97xWPKgHn6uvK31V6zcyPd40lUJd7gpYxbN2VOVxGWO4tyvS9Li4EpsFjXepGTo2tYcOTC4a8YsbQXMJ4XUw== - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colord@^2.9.1: - version "2.9.3" - resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" - integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== - -colorette@^2.0.10, colorette@^2.0.14: - version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -commander@^2.20.0, commander@^2.9.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - -commander@^7.0.0, commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== - -compressible@~2.0.18: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.8.1" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.8.1.tgz#4a45d909ac16509195a9a28bd91094889c180d79" - integrity sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w== - dependencies: - bytes "3.1.2" - compressible "~2.0.18" - debug "2.6.9" - negotiator "~0.6.4" - on-headers "~1.1.0" - safe-buffer "5.2.1" - vary "~1.1.2" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -concat@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/concat/-/concat-1.0.3.tgz#40f3353089d65467695cb1886b45edd637d8cca8" - integrity sha512-f/ZaH1aLe64qHgTILdldbvyfGiGF4uzeo9IuXUloIOLQzFmIPloy9QbZadNsuVv0j5qbKQvQb/H/UYf2UsKTpw== - dependencies: - commander "^2.9.0" - -connect-history-api-fallback@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" - integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== - -consola@^2.15.3: - version "2.15.3" - resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" - integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== - -console-browserify@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" - integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== - -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ== - -content-disposition@~0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4, content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -convert-source-map@^1.7.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - -convert-source-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" - integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== - -cookie-signature@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.7.tgz#ab5dd7ab757c54e60f37ef6550f481c426d10454" - integrity sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA== - -cookie@~0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" - integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== - -core-js-compat@^3.43.0: - version "3.47.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.47.0.tgz#698224bbdbb6f2e3f39decdda4147b161e3772a3" - integrity sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ== - dependencies: - browserslist "^4.28.0" - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cosmiconfig@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" - integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" - -create-ecdh@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" - integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== - dependencies: - bn.js "^4.1.0" - elliptic "^6.5.3" - -create-hash@^1.1.0, create-hash@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -cross-spawn@^7.0.3: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypt@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" - integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== - -crypto-browserify@^3.11.0: - version "3.12.1" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.1.tgz#bb8921bec9acc81633379aa8f52d69b0b69e0dac" - integrity sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ== - dependencies: - browserify-cipher "^1.0.1" - browserify-sign "^4.2.3" - create-ecdh "^4.0.4" - create-hash "^1.2.0" - create-hmac "^1.1.7" - diffie-hellman "^5.0.3" - hash-base "~3.0.4" - inherits "^2.0.4" - pbkdf2 "^3.1.2" - public-encrypt "^4.0.3" - randombytes "^2.1.0" - randomfill "^1.0.4" - -css-declaration-sorter@^6.3.1: - version "6.4.1" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz#28beac7c20bad7f1775be3a7129d7eae409a3a71" - integrity sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g== - -css-loader@^5.2.6: - version "5.2.7" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae" - integrity sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg== - dependencies: - icss-utils "^5.1.0" - loader-utils "^2.0.0" - postcss "^8.2.15" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.1.0" - schema-utils "^3.0.0" - semver "^7.3.5" - -css-select@^4.1.3: - version "4.3.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" - integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== - dependencies: - boolbase "^1.0.0" - css-what "^6.0.1" - domhandler "^4.3.1" - domutils "^2.8.0" - nth-check "^2.0.1" - -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - -css-what@^6.0.1: - version "6.2.2" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.2.2.tgz#cdcc8f9b6977719fdfbd1de7aec24abf756b9dea" - integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssnano-preset-default@^5.2.14: - version "5.2.14" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz#309def4f7b7e16d71ab2438052093330d9ab45d8" - integrity sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A== - dependencies: - css-declaration-sorter "^6.3.1" - cssnano-utils "^3.1.0" - postcss-calc "^8.2.3" - postcss-colormin "^5.3.1" - postcss-convert-values "^5.1.3" - postcss-discard-comments "^5.1.2" - postcss-discard-duplicates "^5.1.0" - postcss-discard-empty "^5.1.1" - postcss-discard-overridden "^5.1.0" - postcss-merge-longhand "^5.1.7" - postcss-merge-rules "^5.1.4" - postcss-minify-font-values "^5.1.0" - postcss-minify-gradients "^5.1.1" - postcss-minify-params "^5.1.4" - postcss-minify-selectors "^5.2.1" - postcss-normalize-charset "^5.1.0" - postcss-normalize-display-values "^5.1.0" - postcss-normalize-positions "^5.1.1" - postcss-normalize-repeat-style "^5.1.1" - postcss-normalize-string "^5.1.0" - postcss-normalize-timing-functions "^5.1.0" - postcss-normalize-unicode "^5.1.1" - postcss-normalize-url "^5.1.0" - postcss-normalize-whitespace "^5.1.1" - postcss-ordered-values "^5.1.3" - postcss-reduce-initial "^5.1.2" - postcss-reduce-transforms "^5.1.0" - postcss-svgo "^5.1.0" - postcss-unique-selectors "^5.1.1" - -cssnano-utils@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" - integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== - -cssnano@^5.0.8: - version "5.1.15" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.15.tgz#ded66b5480d5127fcb44dac12ea5a983755136bf" - integrity sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw== - dependencies: - cssnano-preset-default "^5.2.14" - lilconfig "^2.0.3" - yaml "^1.10.2" - -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^4.1.0, debug@^4.3.1, debug@^4.4.1: - version "4.4.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" - integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - dependencies: - ms "^2.1.3" - -default-gateway@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" - integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== - dependencies: - execa "^5.0.0" - -define-data-property@^1.0.1, define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - -define-properties@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" - integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== - dependencies: - define-data-property "^1.0.1" - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -depd@2.0.0, depd@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - -des.js@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.1.0.tgz#1d37f5766f3bbff4ee9638e871a8768c173b81da" - integrity sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg== - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -destroy@1.2.0, destroy@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-libc@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== - -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - -diffie-hellman@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -dns-packet@^5.2.2: - version "5.6.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" - integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== - dependencies: - "@leichtgewicht/ip-codec" "^2.0.1" - -dom-serializer@^1.0.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" - integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== - -domelementtype@^2.0.1, domelementtype@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domhandler@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.3.0.tgz#6db7ea46e4617eb15cf875df68b2b8524ce0037a" - integrity sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA== - dependencies: - domelementtype "^2.0.1" - -domhandler@^4.2.0, domhandler@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - -domutils@^2.0.0, domutils@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - -dot-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" - integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -dotenv-expand@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" - integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== - -dotenv@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" - integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.5.263: - version "1.5.267" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz#5d84f2df8cdb6bfe7e873706bb21bd4bfb574dc7" - integrity sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw== - -elliptic@^6.5.3, elliptic@^6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.1.tgz#3b8ffb02670bf69e382c7f65bf524c97c5405c06" - integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -encodeurl@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" - integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== - -enhanced-resolve@^5.17.3: - version "5.18.4" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz#c22d33055f3952035ce6a144ce092447c525f828" - integrity sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - -envinfo@^7.7.3: - version "7.21.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.21.0.tgz#04a251be79f92548541f37d13c8b6f22940c3bae" - integrity sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow== - -error-ex@^1.3.1: - version "1.3.4" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" - integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== - dependencies: - is-arrayish "^0.2.1" - -es-define-property@^1.0.0, es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-module-lexer@^1.2.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" - integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== - dependencies: - es-errors "^1.3.0" - -escalade@^3.1.1, escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@^3.0.0, events@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -express@^4.17.3: - version "4.22.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.22.1.tgz#1de23a09745a4fffdb39247b344bb5eaff382069" - integrity sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "~1.20.3" - content-disposition "~0.5.4" - content-type "~1.0.4" - cookie "~0.7.1" - cookie-signature "~1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.3.1" - fresh "~0.5.2" - http-errors "~2.0.0" - merge-descriptors "1.0.3" - methods "~1.1.2" - on-finished "~2.4.1" - parseurl "~1.3.3" - path-to-regexp "~0.1.12" - proxy-addr "~2.0.7" - qs "~6.14.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "~0.19.0" - serve-static "~1.16.2" - setprototypeof "1.2.0" - statuses "~2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-glob@^3.0.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" - integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.8" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-uri@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" - integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== - -fastest-levenshtein@^1.0.12: - version "1.0.16" - resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" - integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== - -fastq@^1.6.0: - version "1.19.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" - integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== - dependencies: - reusify "^1.0.4" - -faye-websocket@^0.11.3: - version "0.11.4" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" - integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== - dependencies: - websocket-driver ">=0.5.1" - -file-loader@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" - integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - -file-type@^12.0.0: - version "12.4.2" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-12.4.2.tgz#a344ea5664a1d01447ee7fb1b635f72feb6169d9" - integrity sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg== - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@~1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.2.tgz#1ebc2228fc7673aac4a472c310cc05b77d852b88" - integrity sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg== - dependencies: - debug "2.6.9" - encodeurl "~2.0.0" - escape-html "~1.0.3" - on-finished "~2.4.1" - parseurl "~1.3.3" - statuses "~2.0.2" - unpipe "~1.0.0" - -find-cache-dir@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" - integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== - dependencies: - commondir "^1.0.1" - make-dir "^3.0.2" - pkg-dir "^4.1.0" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -follow-redirects@^1.0.0: - version "1.15.11" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" - integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== - -for-each@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" - integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== - dependencies: - is-callable "^1.2.7" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fraction.js@^5.3.4: - version "5.3.4" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-5.3.4.tgz#8c0fcc6a9908262df4ed197427bdeef563e0699a" - integrity sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ== - -fresh@0.5.2, fresh@~0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs-extra@^10.0.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" - integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-monkey@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.1.0.tgz#632aa15a20e71828ed56b24303363fb1414e5997" - integrity sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -glob@^7.1.3, glob@^7.2.0: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globby@^10.0.0: - version "10.0.2" - resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" - integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== - dependencies: - "@types/glob" "^7.1.1" - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.0.3" - glob "^7.1.3" - ignore "^5.1.1" - merge2 "^1.2.3" - slash "^3.0.0" - -gopd@^1.0.1, gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -growly@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" - integrity sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw== - -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-symbols@^1.0.3, has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - -hash-base@^3.0.0, hash-base@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.2.tgz#79d72def7611c3f6e3c3b5730652638001b10a74" - integrity sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg== - dependencies: - inherits "^2.0.4" - readable-stream "^2.3.8" - safe-buffer "^5.2.1" - to-buffer "^1.2.1" - -hash-base@~3.0.4: - version "3.0.5" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.5.tgz#52480e285395cf7fba17dc4c9e47acdc7f248a8a" - integrity sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg== - dependencies: - inherits "^2.0.4" - safe-buffer "^5.2.1" - -hash-sum@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04" - integrity sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA== - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -he@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -html-entities@^2.3.2: - version "2.6.0" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.6.0.tgz#7c64f1ea3b36818ccae3d3fb48b6974208e984f8" - integrity sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ== - -html-loader@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/html-loader/-/html-loader-1.3.2.tgz#5a72ebba420d337083497c9aba7866c9e1aee340" - integrity sha512-DEkUwSd0sijK5PF3kRWspYi56XP7bTNkyg5YWSzBdjaSDmvCufep5c4Vpb3PBf6lUL0YPtLwBfy9fL0t5hBAGA== - dependencies: - html-minifier-terser "^5.1.1" - htmlparser2 "^4.1.0" - loader-utils "^2.0.0" - schema-utils "^3.0.0" - -html-minifier-terser@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" - integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== - dependencies: - camel-case "^4.1.1" - clean-css "^4.2.3" - commander "^4.1.1" - he "^1.2.0" - param-case "^3.0.3" - relateurl "^0.2.7" - terser "^4.6.3" - -htmlparser2@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78" - integrity sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q== - dependencies: - domelementtype "^2.0.1" - domhandler "^3.0.0" - domutils "^2.0.0" - entities "^2.0.0" - -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-errors@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-errors@~2.0.0, http-errors@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" - integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== - dependencies: - depd "~2.0.0" - inherits "~2.0.4" - setprototypeof "~1.2.0" - statuses "~2.0.2" - toidentifier "~1.0.1" - -http-parser-js@>=0.5.1: - version "0.5.10" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.10.tgz#b3277bd6d7ed5588e20ea73bf724fcbe44609075" - integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== - -http-proxy-middleware@^2.0.3: - version "2.0.9" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz#e9e63d68afaa4eee3d147f39149ab84c0c2815ef" - integrity sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q== - dependencies: - "@types/http-proxy" "^1.17.8" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@~0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - -ieee754@^1.1.4: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore@^5.1.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" - integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== - -imagemin@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/imagemin/-/imagemin-7.0.1.tgz#f6441ca647197632e23db7d971fffbd530c87dbf" - integrity sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w== - dependencies: - file-type "^12.0.0" - globby "^10.0.0" - graceful-fs "^4.2.2" - junk "^3.1.0" - make-dir "^3.0.0" - p-pipe "^3.0.0" - replace-ext "^1.0.0" - -img-loader@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/img-loader/-/img-loader-4.0.0.tgz#f41fb0737cc8e1d6a8c242f48c29a443640e0638" - integrity sha512-UwRcPQdwdOyEHyCxe1V9s9YFwInwEWCpoO+kJGfIqDrBDqA8jZUsEZTxQ0JteNPGw/Gupmwesk2OhLTcnw6tnQ== - dependencies: - loader-utils "^1.1.0" - -immutable@^5.1.5: - version "5.1.5" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.5.tgz#93ee4db5c2a9ab42a4a783069f3c5d8847d40165" - integrity sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A== - -import-fresh@^3.2.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" - integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-local@^3.0.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" - integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== - -interpret@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" - integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -ipaddr.js@^2.0.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.3.0.tgz#71dce70e1398122208996d1c22f2ba46a24b1abc" - integrity sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg== - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-buffer@~1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - -is-core-module@^2.16.1: - version "2.16.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" - integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== - dependencies: - hasown "^2.0.2" - -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-typed-array@^1.1.14: - version "1.1.15" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" - integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== - dependencies: - which-typed-array "^1.1.16" - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jquery-ui@1.14.2: - version "1.14.2" - resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.14.2.tgz#515288b5c730b720acca6e53a0366827ad834053" - integrity sha512-1gSl7PUjyipa2adSr780Ujk16faicrV7PjPPzPtvWk7tTqBnsqp67NNV9jZK2+BIxUPXWSnIUU/LBCgwgGZE+Q== - dependencies: - jquery ">=1.12.0 <5.0.0" - -jquery@3.7.1, "jquery@>=1.12.0 <5.0.0": - version "3.7.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de" - integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -jsesc@^3.0.2, jsesc@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" - integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== - -json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json5@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" - integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== - dependencies: - minimist "^1.2.0" - -json5@^2.1.2, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -jsonfile@^6.0.1: - version "6.2.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" - integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -junk@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" - integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -klona@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" - integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== - -laravel-mix@6.0.49: - version "6.0.49" - resolved "https://registry.yarnpkg.com/laravel-mix/-/laravel-mix-6.0.49.tgz#d718414858045df9d7467245e13fd4b45bc52c15" - integrity sha512-bBMFpFjp26XfijPvY5y9zGKud7VqlyOE0OWUcPo3vTBY5asw8LTjafAbee1dhfLz6PWNqDziz69CP78ELSpfKw== - dependencies: - "@babel/core" "^7.15.8" - "@babel/plugin-proposal-object-rest-spread" "^7.15.6" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-runtime" "^7.15.8" - "@babel/preset-env" "^7.15.8" - "@babel/runtime" "^7.15.4" - "@types/babel__core" "^7.1.16" - "@types/clean-css" "^4.2.5" - "@types/imagemin-gifsicle" "^7.0.1" - "@types/imagemin-mozjpeg" "^8.0.1" - "@types/imagemin-optipng" "^5.2.1" - "@types/imagemin-svgo" "^8.0.0" - autoprefixer "^10.4.0" - babel-loader "^8.2.3" - chalk "^4.1.2" - chokidar "^3.5.2" - clean-css "^5.2.4" - cli-table3 "^0.6.0" - collect.js "^4.28.5" - commander "^7.2.0" - concat "^1.0.3" - css-loader "^5.2.6" - cssnano "^5.0.8" - dotenv "^10.0.0" - dotenv-expand "^5.1.0" - file-loader "^6.2.0" - fs-extra "^10.0.0" - glob "^7.2.0" - html-loader "^1.3.2" - imagemin "^7.0.1" - img-loader "^4.0.0" - lodash "^4.17.21" - md5 "^2.3.0" - mini-css-extract-plugin "^1.6.2" - node-libs-browser "^2.2.1" - postcss-load-config "^3.1.0" - postcss-loader "^6.2.0" - semver "^7.3.5" - strip-ansi "^6.0.0" - style-loader "^2.0.0" - terser "^5.9.0" - terser-webpack-plugin "^5.2.4" - vue-style-loader "^4.1.3" - webpack "^5.60.0" - webpack-cli "^4.9.1" - webpack-dev-server "^4.7.3" - webpack-merge "^5.8.0" - webpack-notifier "^1.14.1" - webpackbar "^5.0.0-3" - yargs "^17.2.1" - -launch-editor@^2.6.0: - version "2.12.0" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.12.0.tgz#cc740f4e0263a6b62ead2485f9896e545321f817" - integrity sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg== - dependencies: - picocolors "^1.1.1" - shell-quote "^1.8.3" - -lilconfig@^2.0.3, lilconfig@^2.0.5: - version "2.1.0" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" - integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -loader-runner@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.1.tgz#6c76ed29b0ccce9af379208299f07f876de737e3" - integrity sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q== - -loader-utils@^1.0.2, loader-utils@^1.1.0: - version "1.4.2" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" - integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - -loader-utils@^2.0.0, loader-utils@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" - integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== - -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -lower-case@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" - integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== - dependencies: - tslib "^2.0.3" - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" - integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -md5@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" - integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== - dependencies: - charenc "0.0.2" - crypt "0.0.2" - is-buffer "~1.1.6" - -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memfs@^3.4.3: - version "3.6.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" - integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== - dependencies: - fs-monkey "^1.0.4" - -merge-descriptors@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" - integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.2.3, merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^4.0.2, micromatch@^4.0.5, micromatch@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -"mime-db@>= 1.43.0 < 2": - version "1.54.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" - integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== - -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mini-css-extract-plugin@^1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz#83172b4fd812f8fc4a09d6f6d16f924f53990ca8" - integrity sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - webpack-sources "^1.1.0" - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== - -minimatch@*: - version "10.1.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.1.1.tgz#e6e61b9b0c1dcab116b5a7d1458e8b6ae9e73a55" - integrity sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ== - dependencies: - "@isaacs/brace-expansion" "^5.0.0" - -minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.3, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multicast-dns@^7.2.5: - version "7.2.5" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" - integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== - dependencies: - dns-packet "^5.2.2" - thunky "^1.0.2" - -nanoid@^3.3.11: - version "3.3.11" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" - integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -negotiator@~0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" - integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -no-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" - integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== - dependencies: - lower-case "^2.0.2" - tslib "^2.0.3" - -node-addon-api@^7.0.0: - version "7.1.1" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" - integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== - -node-forge@^1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.3.tgz#0ad80f6333b3a0045e827ac20b7f735f93716751" - integrity sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg== - -node-libs-browser@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" - integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== - dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^3.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.1" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.11.0" - vm-browserify "^1.0.1" - -node-notifier@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-9.0.1.tgz#cea837f4c5e733936c7b9005e6545cea825d1af4" - integrity sha512-fPNFIp2hF/Dq7qLDzSg4vZ0J4e9v60gJR+Qx7RbjbWqzPDdEqeVpEx5CFeDAELIl+A/woaaNn1fQ5nEVerMxJg== - dependencies: - growly "^1.3.0" - is-wsl "^2.2.0" - semver "^7.3.2" - shellwords "^0.1.1" - uuid "^8.3.0" - which "^2.0.2" - -node-releases@^2.0.27: - version "2.0.27" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" - integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" - integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== - dependencies: - boolbase "^1.0.0" - -object-inspect@^1.13.3: - version "1.13.4" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" - integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.4: - version "4.1.7" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" - integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== - dependencies: - call-bind "^1.0.8" - call-bound "^1.0.3" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" - has-symbols "^1.1.0" - object-keys "^1.1.1" - -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@2.4.1, on-finished@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-headers@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" - integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -open@^8.0.9: - version "8.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" - integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-pipe@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-3.1.0.tgz#48b57c922aa2e1af6a6404cb7c6bf0eb9cc8e60e" - integrity sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw== - -p-retry@^4.5.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" - integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== - dependencies: - "@types/retry" "0.12.0" - retry "^0.13.1" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -pako@~1.0.5: - version "1.0.11" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - -param-case@^3.0.3: - version "3.0.4" - resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" - integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== - dependencies: - dot-case "^3.0.4" - tslib "^2.0.3" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-asn1@^5.0.0, parse-asn1@^5.1.9: - version "5.1.9" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.9.tgz#8dd24c3ea8da77dffbc708d94eaf232fd6156e95" - integrity sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg== - dependencies: - asn1.js "^4.10.1" - browserify-aes "^1.2.0" - evp_bytestokey "^1.0.3" - pbkdf2 "^3.1.5" - safe-buffer "^5.2.1" - -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parseurl@~1.3.2, parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -pascal-case@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" - integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== - dependencies: - no-case "^3.0.4" - tslib "^2.0.3" - -path-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" - integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@~0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" - integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pbkdf2@^3.1.2, pbkdf2@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.5.tgz#444a59d7a259a95536c56e80c89de31cc01ed366" - integrity sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ== - dependencies: - create-hash "^1.2.0" - create-hmac "^1.1.7" - ripemd160 "^2.0.3" - safe-buffer "^5.2.1" - sha.js "^2.4.12" - to-buffer "^1.2.1" - -picocolors@^1.0.0, picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pkg-dir@^4.1.0, pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -possible-typed-array-names@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" - integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== - -postcss-calc@^8.2.3: - version "8.2.4" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" - integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== - dependencies: - postcss-selector-parser "^6.0.9" - postcss-value-parser "^4.2.0" - -postcss-colormin@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.1.tgz#86c27c26ed6ba00d96c79e08f3ffb418d1d1988f" - integrity sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ== - dependencies: - browserslist "^4.21.4" - caniuse-api "^3.0.0" - colord "^2.9.1" - postcss-value-parser "^4.2.0" - -postcss-convert-values@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz#04998bb9ba6b65aa31035d669a6af342c5f9d393" - integrity sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA== - dependencies: - browserslist "^4.21.4" - postcss-value-parser "^4.2.0" - -postcss-discard-comments@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" - integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== - -postcss-discard-duplicates@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" - integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== - -postcss-discard-empty@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" - integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== - -postcss-discard-overridden@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" - integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== - -postcss-load-config@^3.1.0: - version "3.1.4" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855" - integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg== - dependencies: - lilconfig "^2.0.5" - yaml "^1.10.2" - -postcss-loader@^6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef" - integrity sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q== - dependencies: - cosmiconfig "^7.0.0" - klona "^2.0.5" - semver "^7.3.5" - -postcss-merge-longhand@^5.1.7: - version "5.1.7" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz#24a1bdf402d9ef0e70f568f39bdc0344d568fb16" - integrity sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ== - dependencies: - postcss-value-parser "^4.2.0" - stylehacks "^5.1.1" - -postcss-merge-rules@^5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz#2f26fa5cacb75b1402e213789f6766ae5e40313c" - integrity sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g== - dependencies: - browserslist "^4.21.4" - caniuse-api "^3.0.0" - cssnano-utils "^3.1.0" - postcss-selector-parser "^6.0.5" - -postcss-minify-font-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" - integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-minify-gradients@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" - integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== - dependencies: - colord "^2.9.1" - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-minify-params@^5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz#c06a6c787128b3208b38c9364cfc40c8aa5d7352" - integrity sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw== - dependencies: - browserslist "^4.21.4" - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-minify-selectors@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" - integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-modules-extract-imports@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002" - integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q== - -postcss-modules-local-by-default@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz#d150f43837831dae25e4085596e84f6f5d6ec368" - integrity sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^7.0.0" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz#1bbccddcb398f1d7a511e0a2d1d047718af4078c" - integrity sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA== - dependencies: - postcss-selector-parser "^7.0.0" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - -postcss-normalize-charset@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" - integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== - -postcss-normalize-display-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" - integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-positions@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92" - integrity sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-repeat-style@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2" - integrity sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-string@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" - integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-timing-functions@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" - integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-unicode@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz#f67297fca3fea7f17e0d2caa40769afc487aa030" - integrity sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA== - dependencies: - browserslist "^4.21.4" - postcss-value-parser "^4.2.0" - -postcss-normalize-url@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" - integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== - dependencies: - normalize-url "^6.0.1" - postcss-value-parser "^4.2.0" - -postcss-normalize-whitespace@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" - integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-ordered-values@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38" - integrity sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ== - dependencies: - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-reduce-initial@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz#798cd77b3e033eae7105c18c9d371d989e1382d6" - integrity sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg== - dependencies: - browserslist "^4.21.4" - caniuse-api "^3.0.0" - -postcss-reduce-transforms@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" - integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: - version "6.1.2" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" - integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-selector-parser@^7.0.0: - version "7.1.1" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz#e75d2e0d843f620e5df69076166f4e16f891cb9f" - integrity sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-svgo@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" - integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== - dependencies: - postcss-value-parser "^4.2.0" - svgo "^2.7.0" - -postcss-unique-selectors@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" - integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -postcss@^8.2.14, postcss@^8.2.15: - version "8.5.6" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" - integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== - dependencies: - nanoid "^3.3.11" - picocolors "^1.1.1" - source-map-js "^1.2.1" - -pretty-time@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" - integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -public-encrypt@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" - integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - safe-buffer "^5.1.2" - -punycode@^1.2.4, punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== - -punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -qs@^6.12.3, qs@~6.14.0: - version "6.14.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" - integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== - dependencies: - side-channel "^1.1.0" - -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - integrity sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA== - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -randomfill@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -range-parser@^1.2.1, range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@~2.5.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2" - integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== - dependencies: - bytes "~3.1.2" - http-errors "~2.0.1" - iconv-lite "~0.4.24" - unpipe "~1.0.0" - -readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@^2.3.8: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.6: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" - integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -rechoir@^0.7.0: - version "0.7.1" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" - integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== - dependencies: - resolve "^1.9.0" - -regenerate-unicode-properties@^10.2.2: - version "10.2.2" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz#aa113812ba899b630658c7623466be71e1f86f66" - integrity sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g== - dependencies: - regenerate "^1.4.2" - -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== - -regex-parser@^2.2.11: - version "2.3.1" - resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.3.1.tgz#ee3f70e50bdd81a221d505242cb9a9c275a2ad91" - integrity sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ== - -regexpu-core@^6.3.1: - version "6.4.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.4.0.tgz#3580ce0c4faedef599eccb146612436b62a176e5" - integrity sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA== - dependencies: - regenerate "^1.4.2" - regenerate-unicode-properties "^10.2.2" - regjsgen "^0.8.0" - regjsparser "^0.13.0" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.2.1" - -regjsgen@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab" - integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== - -regjsparser@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.13.0.tgz#01f8351335cf7898d43686bc74d2dd71c847ecc0" - integrity sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q== - dependencies: - jsesc "~3.1.0" - -relateurl@^0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" - integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== - -replace-ext@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" - integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve-url-loader@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz#ee3142fb1f1e0d9db9524d539cfa166e9314f795" - integrity sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg== - dependencies: - adjust-sourcemap-loader "^4.0.0" - convert-source-map "^1.7.0" - loader-utils "^2.0.0" - postcss "^8.2.14" - source-map "0.6.1" - -resolve@^1.22.10, resolve@^1.9.0: - version "1.22.11" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" - integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== - dependencies: - is-core-module "^2.16.1" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - -reusify@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" - integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.3.tgz#9be54e4ba5e3559c8eee06a25cd7648bbccdf5a8" - integrity sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA== - dependencies: - hash-base "^3.1.2" - inherits "^2.0.4" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sass-loader@16.0.7: - version "16.0.7" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-16.0.7.tgz#d1f8723b795805831d41b5825e3d9cd72cb939e7" - integrity sha512-w6q+fRHourZ+e+xA1kcsF27iGM6jdB8teexYCfdUw0sYgcDNeZESnDNT9sUmmPm3ooziwUJXGwZJSTF3kOdBfA== - dependencies: - neo-async "^2.6.2" - -sass@^1.98.0: - version "1.98.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.98.0.tgz#924ce85a3745ccaccd976262fdc1bc0c13aa8e57" - integrity sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A== - dependencies: - chokidar "^4.0.0" - immutable "^5.1.5" - source-map-js ">=0.6.2 <2.0.0" - optionalDependencies: - "@parcel/watcher" "^2.4.1" - -schema-utils@^2.6.5: - version "2.7.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" - integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== - dependencies: - "@types/json-schema" "^7.0.5" - ajv "^6.12.4" - ajv-keywords "^3.5.2" - -schema-utils@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^4.0.0, schema-utils@^4.3.0, schema-utils@^4.3.3: - version "4.3.3" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.3.tgz#5b1850912fa31df90716963d45d9121fdfc09f46" - integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.9.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.1.0" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== - -selfsigned@^2.1.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" - integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== - dependencies: - "@types/node-forge" "^1.3.0" - node-forge "^1" - -semver@^6.0.0, semver@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.3.2, semver@^7.3.5: - version "7.7.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" - integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== - -send@0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -send@~0.19.0: - version "0.19.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.1.tgz#1c2563b2ee4fe510b806b21ec46f355005a369f9" - integrity sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serialize-javascript@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" - integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== - dependencies: - randombytes "^2.1.0" - -serve-index@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" - integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== - dependencies: - accepts "~1.3.4" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.6.2" - mime-types "~2.1.17" - parseurl "~1.3.2" - -serve-static@~1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" - integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== - dependencies: - encodeurl "~2.0.0" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.19.0" - -set-function-length@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -setimmediate@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - -setprototypeof@1.2.0, setprototypeof@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -sha.js@^2.4.0, sha.js@^2.4.12, sha.js@^2.4.8: - version "2.4.12" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.12.tgz#eb8b568bf383dfd1867a32c3f2b74eb52bdbf23f" - integrity sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w== - dependencies: - inherits "^2.0.4" - safe-buffer "^5.2.1" - to-buffer "^1.2.0" - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shell-quote@^1.8.3: - version "1.8.3" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b" - integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== - -shellwords@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" - integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== - -side-channel-list@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" - integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - -side-channel-map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" - integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - -side-channel-weakmap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" - integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - side-channel-map "^1.0.1" - -side-channel@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" - integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.3" - side-channel-list "^1.0.0" - side-channel-map "^1.0.1" - side-channel-weakmap "^1.0.2" - -signal-exit@^3.0.3: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -sockjs@^0.3.24: - version "0.3.24" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" - integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== - dependencies: - faye-websocket "^0.11.3" - uuid "^8.3.2" - websocket-driver "^0.7.4" - -source-list-map@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - -"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - -source-map-support@~0.5.12, source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -"statuses@>= 1.4.0 < 2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - -statuses@~2.0.1, statuses@~2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" - integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== - -std-env@^3.0.1: - version "3.10.0" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.10.0.tgz#d810b27e3a073047b2b5e40034881f5ea6f9c83b" - integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg== - -stream-browserify@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" - integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string_decoder@^1.0.0, string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -style-loader@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c" - integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - -stylehacks@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.1.tgz#7934a34eb59d7152149fa69d6e9e56f2fc34bcc9" - integrity sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw== - dependencies: - browserslist "^4.21.4" - postcss-selector-parser "^6.0.4" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -svgo@^2.7.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== - dependencies: - "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" - picocolors "^1.0.0" - stable "^0.1.8" - -tapable@^2.2.0, tapable@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.0.tgz#7e3ea6d5ca31ba8e078b560f0d83ce9a14aa8be6" - integrity sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg== - -terser-webpack-plugin@^5.2.4, terser-webpack-plugin@^5.3.11: - version "5.3.16" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz#741e448cc3f93d8026ebe4f7ef9e4afacfd56330" - integrity sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q== - dependencies: - "@jridgewell/trace-mapping" "^0.3.25" - jest-worker "^27.4.5" - schema-utils "^4.3.0" - serialize-javascript "^6.0.2" - terser "^5.31.1" - -terser@^4.6.3: - version "4.8.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.1.tgz#a00e5634562de2239fd404c649051bf6fc21144f" - integrity sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw== - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - -terser@^5.31.1, terser@^5.9.0: - version "5.44.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.44.1.tgz#e391e92175c299b8c284ad6ded609e37303b0a9c" - integrity sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.15.0" - commander "^2.20.0" - source-map-support "~0.5.20" - -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - -timers-browserify@^2.0.4: - version "2.0.12" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" - integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== - dependencies: - setimmediate "^1.0.4" - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - integrity sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA== - -to-buffer@^1.2.0, to-buffer@^1.2.1, to-buffer@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.2.2.tgz#ffe59ef7522ada0a2d1cb5dfe03bb8abc3cdc133" - integrity sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw== - dependencies: - isarray "^2.0.5" - safe-buffer "^5.2.1" - typed-array-buffer "^1.0.3" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1, toidentifier@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -tslib@^2.0.3: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" - integrity sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typed-array-buffer@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" - integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== - dependencies: - call-bound "^1.0.3" - es-errors "^1.3.0" - is-typed-array "^1.1.14" - -undici-types@~7.16.0: - version "7.16.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" - integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== - -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz#cb3173fe47ca743e228216e4a3ddc4c84d628cc2" - integrity sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg== - -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" - integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== - dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" - -unicode-match-property-value-ecmascript@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz#65a7adfad8574c219890e219285ce4c64ed67eaa" - integrity sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg== - -unicode-property-aliases-ecmascript@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz#301d4f8a43d2b75c97adfad87c9dd5350c9475d1" - integrity sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ== - -universalify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" - integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== - -unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -update-browserslist-db@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz#cfb4358afa08b3d5731a2ecd95eebf4ddef8033e" - integrity sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -url@^0.11.0: - version "0.11.4" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.4.tgz#adca77b3562d56b72746e76b330b7f27b6721f3c" - integrity sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg== - dependencies: - punycode "^1.4.1" - qs "^6.12.3" - -util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -util@^0.10.4: - version "0.10.4" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" - integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== - dependencies: - inherits "2.0.3" - -util@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" - integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== - dependencies: - inherits "2.0.3" - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@^8.3.0, uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -vm-browserify@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" - integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== - -vue-style-loader@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35" - integrity sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg== - dependencies: - hash-sum "^1.0.2" - loader-utils "^1.0.2" - -watchpack@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.4.tgz#473bda72f0850453da6425081ea46fc0d7602947" - integrity sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - -webpack-cli@^4.9.1: - version "4.10.0" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31" - integrity sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w== - dependencies: - "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^1.2.0" - "@webpack-cli/info" "^1.5.0" - "@webpack-cli/serve" "^1.7.0" - colorette "^2.0.14" - commander "^7.0.0" - cross-spawn "^7.0.3" - fastest-levenshtein "^1.0.12" - import-local "^3.0.2" - interpret "^2.2.0" - rechoir "^0.7.0" - webpack-merge "^5.7.3" - -webpack-dev-middleware@^5.3.4: - version "5.3.4" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517" - integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q== - dependencies: - colorette "^2.0.10" - memfs "^3.4.3" - mime-types "^2.1.31" - range-parser "^1.2.1" - schema-utils "^4.0.0" - -webpack-dev-server@^4.7.3: - version "4.15.2" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz#9e0c70a42a012560860adb186986da1248333173" - integrity sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g== - dependencies: - "@types/bonjour" "^3.5.9" - "@types/connect-history-api-fallback" "^1.3.5" - "@types/express" "^4.17.13" - "@types/serve-index" "^1.9.1" - "@types/serve-static" "^1.13.10" - "@types/sockjs" "^0.3.33" - "@types/ws" "^8.5.5" - ansi-html-community "^0.0.8" - bonjour-service "^1.0.11" - chokidar "^3.5.3" - colorette "^2.0.10" - compression "^1.7.4" - connect-history-api-fallback "^2.0.0" - default-gateway "^6.0.3" - express "^4.17.3" - graceful-fs "^4.2.6" - html-entities "^2.3.2" - http-proxy-middleware "^2.0.3" - ipaddr.js "^2.0.1" - launch-editor "^2.6.0" - open "^8.0.9" - p-retry "^4.5.0" - rimraf "^3.0.2" - schema-utils "^4.0.0" - selfsigned "^2.1.1" - serve-index "^1.9.1" - sockjs "^0.3.24" - spdy "^4.0.2" - webpack-dev-middleware "^5.3.4" - ws "^8.13.0" - -webpack-merge@^5.7.3, webpack-merge@^5.8.0: - version "5.10.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" - integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== - dependencies: - clone-deep "^4.0.1" - flat "^5.0.2" - wildcard "^2.0.0" - -webpack-notifier@^1.14.1: - version "1.15.0" - resolved "https://registry.yarnpkg.com/webpack-notifier/-/webpack-notifier-1.15.0.tgz#72644a1a4ec96b3528704d28f79da5e70048e8ee" - integrity sha512-N2V8UMgRB5komdXQRavBsRpw0hPhJq2/SWNOGuhrXpIgRhcMexzkGQysUyGStHLV5hkUlgpRiF7IUXoBqyMmzQ== - dependencies: - node-notifier "^9.0.0" - strip-ansi "^6.0.0" - -webpack-sources@^1.1.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== - dependencies: - source-list-map "^2.0.0" - source-map "~0.6.1" - -webpack-sources@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.3.3.tgz#d4bf7f9909675d7a070ff14d0ef2a4f3c982c723" - integrity sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg== - -webpack@^5.60.0: - version "5.103.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.103.0.tgz#17a7c5a5020d5a3a37c118d002eade5ee2c6f3da" - integrity sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw== - dependencies: - "@types/eslint-scope" "^3.7.7" - "@types/estree" "^1.0.8" - "@types/json-schema" "^7.0.15" - "@webassemblyjs/ast" "^1.14.1" - "@webassemblyjs/wasm-edit" "^1.14.1" - "@webassemblyjs/wasm-parser" "^1.14.1" - acorn "^8.15.0" - acorn-import-phases "^1.0.3" - browserslist "^4.26.3" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.17.3" - es-module-lexer "^1.2.1" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.11" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.3.1" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^4.3.3" - tapable "^2.3.0" - terser-webpack-plugin "^5.3.11" - watchpack "^2.4.4" - webpack-sources "^3.3.3" - -webpackbar@^5.0.0-3: - version "5.0.2" - resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-5.0.2.tgz#d3dd466211c73852741dfc842b7556dcbc2b0570" - integrity sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ== - dependencies: - chalk "^4.1.0" - consola "^2.15.3" - pretty-time "^1.1.0" - std-env "^3.0.1" - -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -which-typed-array@^1.1.16: - version "1.1.19" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" - integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== - dependencies: - available-typed-arrays "^1.0.7" - call-bind "^1.0.8" - call-bound "^1.0.4" - for-each "^0.3.5" - get-proto "^1.0.1" - gopd "^1.2.0" - has-tostringtag "^1.0.2" - -which@^2.0.1, which@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wildcard@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" - integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -ws@^8.13.0: - version "8.18.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" - integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== - -xtend@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yaml@^1.10.0, yaml@^1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.2.1: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" From 4036c5b6bb6e2b41d06d1d55f0b28079bf48d23e Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 1 Apr 2026 11:35:43 +0100 Subject: [PATCH 039/168] feature: collapsable menu items --- resources/assets/sass/app.scss | 114 +++++++-- .../views/service/includes/sidebar.blade.php | 229 +++++++++++++++--- 2 files changed, 290 insertions(+), 53 deletions(-) diff --git a/resources/assets/sass/app.scss b/resources/assets/sass/app.scss index a49682d7e..9685922f9 100644 --- a/resources/assets/sass/app.scss +++ b/resources/assets/sass/app.scss @@ -343,29 +343,6 @@ span.user-array { min-height: 100%; } -#sidebar { - flex: 0 0 300px; - font-weight: 600; - background-color: $arc_white; - border-right: 1px solid #ededed; - ul { - padding-left: 0; - margin-top: 1em; - li { - color: #656565; - display: block; - padding: 1em 2em; - &:hover, - &:active { - background-color: #f5f5f5; - } - } - span { - padding-right: 10px; - } - } -} - #main-content { background-color: $arc_bg; flex: 1; @@ -416,3 +393,94 @@ tr.disabled { button.remove { background-color: #d2232a; } + +#sidebar { + flex: 0 0 300px; + font-weight: 600; + background-color: $arc_white; + border-right: 1px solid #ededed; + + ul { + padding-left: 0; + margin-top: 1em; + + li { + color: #656565; + display: block; + padding: 1em 2em; + &:hover, + &:active { + background-color: #f5f5f5; + } + } + + span { + padding-right: 0px; + } + } +} + +.sidebar-heading { + cursor: pointer; + user-select: none; + // Override the li padding so we control spacing ourselves + padding: 0.75em 1.5em !important; + font-weight: 700; + text-transform: uppercase; + font-size: 0.75em; + letter-spacing: 0.08em; + color: #999; + + // Flex layout so icon | label | caret sit cleanly in one row + display: flex !important; + align-items: center; + gap: 8px; + + .glyphicon:first-child { + padding-right: 0; // gap handles spacing instead + flex-shrink: 0; + } + + .sidebar-heading-label { + flex: 1; // pushes caret to the far right + } + + .caret { + flex-shrink: 0; + transition: transform 0.2s ease; + } + + // Rotate caret when section is collapsed + &.collapsed .caret { + transform: rotate(-90deg); + } +} + +.sidebar-section--dev .sidebar-heading { + color: #d9534f; +} + +.sidebar-subheading { + padding: 4px 1.5em 4px 2.5em !important; + font-size: 0.7em; + opacity: 0.55; + text-transform: uppercase; + letter-spacing: 0.06em; + &:hover { + background-color: transparent !important; // subheading isn't a link + } +} + +.sidebar-item--danger a { + color: #d9534f; +} + +.sidebar-message { + padding: 0.5em 1.5em !important; + font-size: 0.85em; + font-style: italic; + color: #999; + &:hover { + background-color: transparent !important; + } +} diff --git a/resources/views/service/includes/sidebar.blade.php b/resources/views/service/includes/sidebar.blade.php index d31032a7e..7313478de 100644 --- a/resources/views/service/includes/sidebar.blade.php +++ b/resources/views/service/includes/sidebar.blade.php @@ -1,35 +1,204 @@ From 663da484527c9017bf1b84970f47f40cf9338c2e Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 2 Apr 2026 10:21:00 +0100 Subject: [PATCH 040/168] refactor: use new format for routes --- routes/api.php | 129 ++++++----------- routes/channels.php | 4 +- routes/console.php | 2 + routes/data.php | 80 +++++----- routes/service.php | 323 ++++++++++++++--------------------------- routes/store.php | 345 ++++++++++++++++++-------------------------- 6 files changed, 348 insertions(+), 535 deletions(-) diff --git a/routes/api.php b/routes/api.php index ac03dfb0d..36fcc4b07 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,102 +1,69 @@ 'api.login', - 'uses' => 'Auth\LoginController@login', -]); - -Route::post('login/refresh', [ - 'as' => 'api.login.refresh', - 'uses' => 'Auth\LoginController@refresh', -]); - -Route::post('user/lost_password', [ - 'as' => 'api.user.lost_password', - 'uses' => 'Auth\ForgotPasswordController@sendResetLinkEmail' -]); +Route::post('login', [LoginController::class, 'login'])->name('api.login'); +Route::post('login/refresh', [LoginController::class, 'refresh'])->name('api.login.refresh'); +Route::post('user/lost_password', [ForgotPasswordController::class, 'sendResetLinkEmail']) + ->name('api.user.lost_password'); +Route::post('user/lost_password/reset', [ResetPasswordController::class, 'reset'])->name('api.user.reset_password'); -Route::post('user/lost_password/reset', [ - 'as' => 'api.user.reset_password', - 'uses' => 'Auth\ResetPasswordController@reset' -]); +// Authenticated routes +Route::middleware('auth:api')->group(function () { -/** Authentication required --------------------------------------------- */ + Route::post('logout', [LoginController::class, 'logout'])->name('api.logout'); -Route::group(['middleware' => 'auth:api'], function () { + Route::get('queue/{jobStatus}', [QueueController::class, 'show'])->name('api.queued-task.show'); - Route::get('queue/{jobStatus}', [ - 'as' => 'api.queued-task.show', - 'uses' => 'QueueController@show' - ]); + Route::get('traders', [TraderController::class, 'index'])->name('api.traders'); - Route::post('logout', [ - 'as' => 'api.logout', - 'uses' => 'Auth\LoginController@logout', - ]); + Route::middleware('can:view,trader')->group(function () { + Route::get('traders/{trader}', [TraderController::class, 'show']) + ->name('api.trader') + ->whereNumber('trader'); - Route::get('traders', [ - 'as' => 'api.traders', - 'uses' => 'TraderController@index', - ]); + Route::get('traders/{trader}/vouchers', [TraderController::class, 'showVouchers']) + ->name('api.trader.vouchers') + ->middleware(['setEtag', 'ifNoneMatch']) + ->whereNumber('trader'); - Route::group(['middleware' => 'can:view,trader'], function () { - Route::get('traders/{trader}', [ - 'as' => 'api.trader', - 'uses' => 'TraderController@show', - // $user and App\Trader sent implicitly to policy. - ])->where('trader', '^[0-9]+$'); + Route::get('traders/{trader}/voucher-history', [TraderController::class, 'showVoucherHistory']) + ->name('api.trader.voucher-history') + ->middleware(['setEtag', 'ifNoneMatch']) + ->whereNumber('trader'); - Route::get('traders/{trader}/vouchers', [ - 'as' => 'api.trader.vouchers', - 'uses' => 'TraderController@showVouchers', - ])->middleware(['setEtag', 'ifNoneMatch']) - ->where('trader', '^[0-9]+$'); - - Route::get('traders/{trader}/voucher-history', [ - 'as' => 'api.trader.voucher-history', - 'uses' => 'TraderController@showVoucherHistory', - ])->middleware(['setEtag', 'ifNoneMatch']) - ->where('trader', '^[0-9]+$'); - - Route::post('traders/{trader}/voucher-history-email', [ - 'as' => 'api.trader.voucher-history-email', - 'uses' => 'TraderController@emailVoucherHistory', - ])->middleware(['setEtag', 'ifNoneMatch']) - ->where('trader', '^[0-9]+$'); + Route::post('traders/{trader}/voucher-history-email', [TraderController::class, 'emailVoucherHistory']) + ->name('api.trader.voucher-history-email') + ->middleware(['setEtag', 'ifNoneMatch']) + ->whereNumber('trader'); }); - /** - * Legacy transition for old clients - */ - Route::post('vouchers', [ - 'as' => 'api.voucher.transition', - 'uses' => 'VoucherController@legacyTransition', - ])->middleware('can:collect,App\Voucher'); - - /** - * new voucher transition routes - */ - Route::post('vouchers/transitions', [ - 'as' => 'api.vouchers.transition-responses.store', - 'uses' => 'TransitionController@store', - ])->middleware('can:collect,App\Voucher'); + // Legacy transition for old clients + Route::post('vouchers', [VoucherController::class, 'legacyTransition']) + ->name('api.voucher.transition') + ->middleware('can:collect,App\Voucher'); - Route::get('vouchers/transitions/{jobStatus}', [ - 'as' => 'api.vouchers.transition-response.show', - 'uses' => 'TransitionController@show', - ])->where('jobStatus', '^[0-9]+$') - //->middleware('can:collect,App\Voucher'); - ; + // New voucher transition routes + Route::post('vouchers/transitions', [TransitionController::class, 'store']) + ->name('api.vouchers.transition-responses.store') + ->middleware('can:collect,App\Voucher'); + Route::get('vouchers/transitions/{jobStatus}', [TransitionController::class, 'show']) + ->name('api.vouchers.transition-response.show') + ->whereNumber('jobStatus'); - Route::put('log', [ - 'as' => 'api.log', - 'uses' => 'LoggingController@log', - ]); + Route::put('log', [LoggingController::class, 'log'])->name('api.log'); }); diff --git a/routes/channels.php b/routes/channels.php index f16a20b9b..5b4bc1cdd 100644 --- a/routes/channels.php +++ b/routes/channels.php @@ -1,5 +1,7 @@ id === (int) $id; }); diff --git a/routes/console.php b/routes/console.php index 75dd0cded..20064aa0e 100644 --- a/routes/console.php +++ b/routes/console.php @@ -1,6 +1,7 @@ comment(Inspiring::quote()); })->describe('Display an inspiring quote'); + diff --git a/routes/data.php b/routes/data.php index 9dfe6ad32..acdd5ff93 100644 --- a/routes/data.php +++ b/routes/data.php @@ -1,54 +1,52 @@ namespace() has been removed in Laravel 12. Controllers are now +// referenced directly via fully-qualified class imports above. -// route names have data.x ; append Data/ to find the controllers ; admin guards Route::name('data.') - ->namespace('Data') ->middleware('auth:admin') ->group(function () { - // For now these routes are only available in dev and staging environs. - Route::resource('vouchers', 'VoucherController', [ - 'only' => ['index', 'show',], - ]); - - Route::resource('users', 'UserController', [ - 'only' => ['index',], - ]); - - Route::resource('markets', 'MarketController', [ - 'only' => ['index',], - ]); - - Route::resource('traders', 'TraderController', [ - 'only' => ['index',], - ]); - - // Temp route for demo only. - Route::name('reset') - ->get('reset', function () { - $process = new Process(['php', '../artisan', 'migrate:refresh', '--seed', '--force']); - $process->run(); - $process = new Process(['php', '../artisan', 'passport:install']); - $process->run(); - - $new_secret = DB::table('oauth_clients')->where('id', 2)->pluck('secret')[0]; - $env_file_path = base_path('.env'); - $old_secret = env('PASSWORD_CLIENT_SECRET'); - file_put_contents($env_file_path, preg_replace( - "/^PASSWORD_CLIENT_SECRET={$old_secret}/m", - "PASSWORD_CLIENT_SECRET={$new_secret}", - file_get_contents($env_file_path) - )); - - return Redirect::route('admin.dashboard') - ->with('message', 'Reseeded @' . Carbon::now()); - }); + + // For now these routes are only available in dev and staging environments. + Route::resource('vouchers', VoucherController::class)->only(['index', 'show']); + Route::resource('users', UserController::class)->only(['index']); + Route::resource('markets', MarketController::class)->only(['index']); + Route::resource('traders', TraderController::class)->only(['index']); + + // Temporary route for demo only. + Route::get('reset', static function () { + $process = new Process(['php', '../artisan', 'migrate:refresh', '--seed', '--force']); + $process->run(); + + $process = new Process(['php', '../artisan', 'passport:install']); + $process->run(); + + $newSecret = DB::table('oauth_clients')->where('id', 2)->pluck('secret')[0]; + $envFilePath = base_path('.env'); + $oldSecret = env('PASSWORD_CLIENT_SECRET'); + + file_put_contents($envFilePath, preg_replace( + "/^PASSWORD_CLIENT_SECRET={$oldSecret}/m", + "PASSWORD_CLIENT_SECRET={$newSecret}", + file_get_contents($envFilePath) + )); + + return Redirect::route('admin.dashboard') + ->with('message', 'Reseeded @' . Carbon::now()); + })->name('reset'); }); diff --git a/routes/service.php b/routes/service.php index dbc9dbae5..dad3ccb65 100644 --- a/routes/service.php +++ b/routes/service.php @@ -1,5 +1,19 @@ 'admin.login', - 'uses' => 'Auth\LoginController@showLoginForm', -]); -Route::post('login', 'Auth\LoginController@login'); - -Route::get('/', 'AdminController@index')->name('admin.dashboard'); - -Route::get('version', [ - 'as' => 'version', - 'uses' => 'VersionController@version', -]); - -// Admin (Service) Password Reset Routes... -Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm') - ->name('admin.password.request') -; -Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail') - ->name('admin.password.email') -; -Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm') + +Route::get('login', [LoginController::class, 'showLoginForm'])->name('admin.login'); +Route::post('login', [LoginController::class, 'login']); + +Route::get('/', [AdminController::class, 'index'])->name('admin.dashboard'); + +Route::get('version', [VersionController::class, 'version'])->name('version'); + +// Password Reset +Route::get('password/reset', [ForgotPasswordController::class, 'showLinkRequestForm']) + ->name('admin.password.request'); +Route::post('password/email', [ForgotPasswordController::class, 'sendResetLinkEmail']) + ->name('admin.password.email'); +Route::get('password/reset/{token}', [ResetPasswordController::class, 'showResetForm']) ->name('admin.password.reset') ->where('token', '[0-9a-f]{64}'); -; -Route::post('password/reset', 'Auth\ResetPasswordController@reset'); +Route::post('password/reset', [ResetPasswordController::class, 'reset']); + +// +Route::group(['middleware' => 'auth:admin'], static function () { + + // Must be logged in to log out. + Route::post('logout', [LoginController::class, 'logout'])->name('admin.logout'); -Route::group(['middleware' => 'auth:admin'], function () { // Voucher Management - Route::get('vouchers', [ - 'as' => 'admin.vouchers.index', - 'uses' => 'Admin\VouchersController@index', - ]); + Route::get('vouchers', [VouchersController::class, 'index'])->name('admin.vouchers.index'); // ...create form - Route::get('vouchers/create', [ - 'as' => 'admin.vouchers.create', - 'uses' => 'Admin\VouchersController@create', - ]); + Route::get('vouchers/create', [VouchersController::class, 'create'])->name('admin.vouchers.create'); // ...void form - Route::get('vouchers/void', [ - 'as' => 'admin.vouchers.void', - 'uses' => 'Admin\VouchersController@void', - ]); + Route::get('vouchers/void', [VouchersController::class, 'void'])->name('admin.vouchers.void'); + + Route::get('vouchers/search', [VouchersController::class, 'search'])->name('admin.vouchers.search'); // ...store batch of printed - Route::post('vouchers', [ - 'as' => 'admin.vouchers.storebatch', - 'uses' => 'Admin\VouchersController@storeBatch', - ]); + Route::post('vouchers', [VouchersController::class, 'storeBatch'])->name('admin.vouchers.storebatch'); // ...patch because changing state of a partial collection of vouchers. - Route::patch('vouchers', [ - 'as' => 'admin.vouchers.retirebatch', - 'uses' => 'Admin\VouchersController@retireBatch', - ]); - Route::get('vouchers/{id}', [ - 'as' => 'service.vouchers.viewone', - 'uses' => 'Admin\VouchersController@viewOne', - ])->where('id', '^[0-9]+$'); - - Route::get('vouchers/search', [ - 'as' => 'admin.vouchers.search', - 'uses' => 'Admin\VouchersController@search', - ]); + Route::patch('vouchers', [VouchersController::class, 'retireBatch'])->name('admin.vouchers.retirebatch'); + Route::get('vouchers/{voucher}', [VouchersController::class, 'viewOne']) + ->name('service.vouchers.viewone') + ->whereNumber('voucher'); //Payment Management - Route::get('payments',[ - 'as' =>'admin.payments.index', - 'uses' =>'Admin\PaymentsController@index', - ]); - - Route::get('payments/payment-request/{paymentUuid}', [ - 'as' => 'admin.payment-request.show', - 'uses' => 'Admin\PaymentsController@show' - ]); - - Route::put('payments/payment-request/{paymentUuid}', [ - 'as' => 'admin.payment-request.update', - 'uses' => 'Admin\PaymentsController@update' - ]); - - Route::get('payments/trader-payment-history/{trader}', [ - 'as' => 'admin.trader-payment-history.show', - 'uses' => 'Admin\TradersController@traderHistory' - ]); + Route::get('payments', [PaymentsController::class, 'index'])->name('admin.payments.index'); + Route::get('payments/payment-request/{paymentUuid}', [PaymentsController::class, 'show']) + ->name('admin.payment-request.show'); + Route::put('payments/payment-request/{paymentUuid}', [PaymentsController::class, 'update']) + ->name('admin.payment-request.update'); + Route::get('payments/trader-payment-history/{trader}', [TradersController::class, 'traderHistory']) + ->name('admin.trader-payment-history.show') + ->whereNumber('trader'); // Worker Management - Route::get('workers', [ - 'as' => 'admin.centreusers.index', - 'uses' => 'Admin\CentreUsersController@index', - ]); - Route::get('workers/create', [ - 'as' => 'admin.centreusers.create', - 'uses' => 'Admin\CentreUsersController@create', - ]); - Route::post('workers', [ - 'as' => 'admin.centreusers.store', - 'uses' => 'Admin\CentreUsersController@store', - ]); - Route::put('workers/{id}', [ - 'as' => 'admin.centreusers.update', - 'uses' => 'Admin\CentreUsersController@update', - ])->where('id', '^[0-9]+$'); - Route::get('workers/{id}/edit', [ - 'as' => 'admin.centreusers.edit', - 'uses' => 'Admin\CentreUsersController@edit', - ])->where('id', '^[0-9]+$'); - Route::get('workers/download', [ - 'as' => 'admin.centreusers.download', - 'uses' => 'Admin\CentreUsersController@download', - ]); - - Route::get('workers/{id}/toggle', [ - 'as' => 'admin.centreusers.toggle', - 'uses' => 'Admin\CentreUsersController@toggle', - ])->where('id', '^[0-9]+$'); - - Route::get('workers/{id}/delete', [ - 'as' => 'admin.centreusers.delete', - 'uses' => 'Admin\CentreUsersController@delete', - ])->where('id', '^[0-9]+$'); + Route::get('workers', [CentreUsersController::class, 'index'])->name('admin.centreusers.index'); + Route::get('workers/create', [CentreUsersController::class, 'create'])->name('admin.centreusers.create'); + Route::get('workers/download', [CentreUsersController::class, 'download'])->name('admin.centreusers.download'); + Route::post('workers', [CentreUsersController::class, 'store'])->name('admin.centreusers.store'); + Route::put('workers/{id}', [CentreUsersController::class, 'update']) + ->name('admin.centreusers.update') + ->whereNumber('id'); + Route::get('workers/{id}/edit', [CentreUsersController::class, 'edit']) + ->name('admin.centreusers.edit') + ->whereNumber('id'); + Route::get('workers/{id}/toggle', [CentreUsersController::class, 'toggle']) + ->name('admin.centreusers.toggle') + ->whereNumber('id'); + Route::get('workers/{id}/delete', [CentreUsersController::class, 'delete']) + ->name('admin.centreusers.delete') + ->whereNumber('id'); // Centre Management - Route::get('centres', [ - 'as' => 'admin.centres.index', - 'uses' => 'Admin\CentresController@index', - ]); - Route::get('centres/create', [ - 'as' => 'admin.centres.create', - 'uses' => 'Admin\CentresController@create', - ]); - Route::get('centres/{id}/neighbours', [ - 'as' => 'admin.centre_neighbours.index', - 'uses' => 'Admin\CentresController@getNeighboursAsJson' - ])->where('id', '^[0-9]+$'); - Route::post('centres', [ - 'as' => 'admin.centres.store', - 'uses' => 'Admin\CentresController@store', - ]); - Route::put('centres/{id}/update', [ - 'as' => 'admin.centres.update', - 'uses' => 'Admin\CentresController@update', - ])->where('id', '^[0-9]+$'); - Route::get('centres/{id}/edit', [ - 'as' => 'admin.centres.edit', - 'uses' => 'Admin\CentresController@edit', - ])->where('id', '^[0-9]+$'); + Route::get('centres', [CentresController::class, 'index'])->name('admin.centres.index'); + Route::get('centres/create', [CentresController::class, 'create'])->name('admin.centres.create'); + Route::post('centres', [CentresController::class, 'store'])->name('admin.centres.store'); + Route::get('centres/{id}/neighbours', [CentresController::class, 'getNeighboursAsJson']) + ->name('admin.centre_neighbours.index') + ->whereNumber('id'); + Route::put('centres/{id}/update', [CentresController::class, 'update']) + ->name('admin.centres.update') + ->whereNumber('id'); + Route::get('centres/{id}/edit', [CentresController::class, 'edit']) + ->name('admin.centres.edit') + ->whereNumber('id'); // Sponsor Management - Route::get('sponsors', [ - 'as' => 'admin.sponsors.index', - 'uses' => 'Admin\SponsorsController@index', - ]); - Route::get('sponsors/create', [ - 'as' => 'admin.sponsors.create', - 'uses' => 'Admin\SponsorsController@create', - ]); - Route::post('sponsors', [ - 'as' => 'admin.sponsors.store', - 'uses' => 'Admin\SponsorsController@store', - ]); - Route::get('sponsors/{id}', [ - 'as' => 'admin.sponsors.edit', - 'uses' => 'Admin\SponsorsController@edit', - ])->where('id', '^[0-9]+$'); - Route::put('sponsors/{id}', [ - 'as' => 'admin.sponsors.update', - 'uses' => 'Admin\SponsorsController@update', - ])->where('id', '^[0-9]+$'); - - Route::post('logout', [ - 'as' => 'admin.logout', - 'uses' => 'Auth\LoginController@logout', - ]); + Route::get('sponsors', [SponsorsController::class, 'index'])->name('admin.sponsors.index'); + Route::get('sponsors/create', [SponsorsController::class, 'create'])->name('admin.sponsors.create'); + Route::post('sponsors', [SponsorsController::class, 'store'])->name('admin.sponsors.store'); + Route::get('sponsors/{id}', [SponsorsController::class, 'edit']) + ->name('admin.sponsors.edit') + ->whereNumber('id'); + Route::put('sponsors/{id}', [SponsorsController::class, 'update']) + ->name('admin.sponsors.update') + ->whereNumber('id'); // Deliveries Management - Route::get('deliveries', [ - 'as' => 'admin.deliveries.index', - 'uses' => 'Admin\DeliveriesController@index', - ]); - Route::get('deliveries/create', [ - 'as' => 'admin.deliveries.create', - 'uses' => 'Admin\DeliveriesController@create', - ]); - Route::post('deliveries/store', [ - 'as' => 'admin.deliveries.store', - 'uses' => 'Admin\DeliveriesController@store', - ]); + Route::get('deliveries', [DeliveriesController::class, 'index'])->name('admin.deliveries.index'); + Route::get('deliveries/create', [DeliveriesController::class, 'create'])->name('admin.deliveries.create'); + Route::post('deliveries/store', [DeliveriesController::class, 'store'])->name('admin.deliveries.store'); // Market Management - Route::get('markets', [ - 'as' => 'admin.markets.index', - 'uses' => 'Admin\MarketsController@index', - ]); - Route::get('markets/create', [ - 'as' => 'admin.markets.create', - 'uses' => 'Admin\MarketsController@create', - ]); - Route::post('markets', [ - 'as' => 'admin.markets.store', - 'uses' => 'Admin\MarketsController@store', - ]); - Route::get('markets/{id}/edit', [ - 'as' => 'admin.markets.edit', - 'uses' => 'Admin\MarketsController@edit', - ])->where('id', '^[0-9]+$'); - Route::put('markets/{id}', [ - 'as' => 'admin.markets.update', - 'uses' => 'Admin\MarketsController@update', - ])->where('id', '^[0-9]+$'); + Route::get('markets', [MarketsController::class, 'index'])->name('admin.markets.index'); + Route::get('markets/create', [MarketsController::class, 'create'])->name('admin.markets.create'); + Route::post('markets', [MarketsController::class, 'store'])->name('admin.markets.store'); + Route::get('markets/{id}/edit', [MarketsController::class, 'edit']) + ->name('admin.markets.edit') + ->whereNumber('id'); + Route::put('markets/{id}', [MarketsController::class, 'update']) + ->name('admin.markets.update') + ->whereNumber('id'); // Trader Management - Route::get('traders', [ - 'as' => 'admin.traders.index', - 'uses' => 'Admin\TradersController@index', - ]); - Route::get('traders/create', [ - 'as' => 'admin.traders.create', - 'uses' => 'Admin\TradersController@create', - ]); - Route::post('traders', [ - 'as' => 'admin.traders.store', - 'uses' => 'Admin\TradersController@store', - ]); - Route::get('traders/{id}/edit', [ - 'as' => 'admin.traders.edit', - 'uses' => 'Admin\TradersController@edit', - ])->where('id', '^[0-9]+$'); - Route::put('traders/{id}', [ - 'as' => 'admin.traders.update', - 'uses' => 'Admin\TradersController@update', - ])->where('id', '^[0-9]+$'); - Route::get('traders/download', [ - 'as' => 'admin.traders.download', - 'uses' => 'Admin\TradersController@download', - ]); + Route::get('traders', [TradersController::class, 'index'])->name('admin.traders.index'); + Route::get('traders/create', [TradersController::class, 'create'])->name('admin.traders.create'); + Route::get('traders/download', [TradersController::class, 'download'])->name('admin.traders.download'); + Route::post('traders', [TradersController::class, 'store'])->name('admin.traders.store'); + Route::get('traders/{id}/edit', [TradersController::class, 'edit']) + ->name('admin.traders.edit') + ->whereNumber('id'); + Route::put('traders/{id}', [TradersController::class, 'update']) + ->name('admin.traders.update') + ->whereNumber('id'); }); diff --git a/routes/store.php b/routes/store.php index bb7d6ef64..1facfbd41 100644 --- a/routes/store.php +++ b/routes/store.php @@ -1,204 +1,147 @@ 'store.login', - 'uses' => 'Auth\LoginController@showLoginForm', -]); - -Route::post('login', 'Auth\LoginController@login'); - -Route::post('logout', [ - 'as' => 'store.logout', - 'uses' => 'Auth\LoginController@logout', -]); - -// Admin (Store) Password Reset Routes... -Route::get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm') - ->name('store.password.request') -; -Route::post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail') - ->name('store.password.email') -; -Route::get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm') +use App\Http\Controllers\Store\Auth\LoginController; +use App\Http\Controllers\Store\Auth\ForgotPasswordController; +use App\Http\Controllers\Store\Auth\ResetPasswordController; +use App\Http\Controllers\Store\DashboardController; +use App\Http\Controllers\Store\SessionController; +use App\Http\Controllers\Store\RegistrationController; +use App\Http\Controllers\Store\FamilyController; +use App\Http\Controllers\Store\BundleController; +use App\Http\Controllers\Store\HistoryController; +use App\Http\Controllers\Store\CentreController; +use App\Http\Controllers\Store\VoucherController; + +/* +|-------------------------------------------------------------------------- +| Store Routes +|-------------------------------------------------------------------------- +*/ + +// Authentication +Route::get('login', [LoginController::class, 'showLoginForm'])->name('store.login'); +Route::post('login', [LoginController::class, 'login']); +Route::post('logout', [LoginController::class, 'logout'])->name('store.logout'); + +// Password Reset +Route::get('password/reset', [ForgotPasswordController::class, 'showLinkRequestForm']) + ->name('store.password.request'); +Route::post('password/email', [ForgotPasswordController::class, 'sendResetLinkEmail']) + ->name('store.password.email'); +Route::get('password/reset/{token}', [ResetPasswordController::class, 'showResetForm']) ->name('store.password.reset') - ->where('token', '[0-9a-f]{64}') -; -Route::post('password/reset', 'Auth\ResetPasswordController@reset'); - -// Store Dashboard route -// Default redirect to Service Dashboard - -Route::get('/', static function () { - $route = 'store.login'; - return redirect()->route($route); -})->name('store.base'); - - -// Route groups for authorised routes -Route::group(['middleware' => 'auth:store'], function () { - Route::get( - 'dashboard', - 'DashboardController@index' - )->name('store.dashboard'); - - // Route to update the CentreUser's Session - Route::put('/session', [ - 'as' => 'store.session.put', - 'uses' => 'SessionController@update' - ]); - - Route::get('/registrations', [ - 'as' => 'store.registration.index', - 'uses' => 'RegistrationController@index', - ]); - - Route::post('/registrations', [ - 'as' => 'store.registration.store', - 'uses' => 'RegistrationController@store', - ]); - - Route::get('/registrations/create', [ - 'as' => 'store.registration.create', - 'uses' => 'RegistrationController@create', - ]); - - // Batch print Family Forms for User Centre - Route::get('/registrations/print', [ - 'as' => 'store.registrations.print', - 'uses' => 'RegistrationController@printBatchIndividualFamilyForms', - ]); - - Route::group( - // Must be able to access this registration - ['middleware' => 'can:readOrUpdate,registration'], - function () { - - // Edit a specific thing - Route::get('/registrations/{registration}/edit', [ - 'as' => 'store.registration.edit', - 'uses' => 'RegistrationController@edit', - ])->where('registration', '^[0-9]+$'); - - // Update a specific registration - Route::put('/registrations/{registration}', [ - 'as' => 'store.registration.update', - 'uses' => 'RegistrationController@update', - ])->where('registration', '^[0-9]+$'); - - // View a specific registration - Route::get('/registrations/{registration}/view', [ - 'as' => 'store.registration.view', - 'uses' => 'RegistrationController@view', - ])->where('registration', '^[0-9]+$'); - - // Update (deactivate) a specific Registration's Family - Route::put('/registrations/{registration}/family', [ - 'as' => 'store.registration.family', - 'uses' => 'FamilyController@update', - ])->where('registration', '^[0-9]+$'); - - // Rejoin a specific Registration's Family - Route::put('/registrations/{registration}/rejoin', [ - 'as' => 'store.registration.rejoin', - 'uses' => 'FamilyController@rejoin', - ])->where('registration', '^[0-9]+$'); - - // Print a specific registration - Route::get('/registrations/{registration}/print', [ - 'as' => 'store.registration.print', - 'uses' => 'RegistrationController@printOneIndividualFamilyForm', - ])->where('registration', '^[0-9]+$'); - - // PUTS (and replaces!) the currentBundle of vouchers! - Route::put('/registrations/{registration}/vouchers', [ - 'as' => 'store.registration.vouchers.put', - 'uses' => 'BundleController@update', - ])->where('registration', '^[0-9]+$'); - - // View a registrations vouchers - Route::get('/registrations/{registration}/voucher-manager', [ - 'as' => 'store.registration.voucher-manager', - 'uses' => 'BundleController@create' - ])->where('registration', '^[0-9]+$'); - - // Fetches a registration's collection history - Route::get( - '/registrations/{registration}/collection-history', - 'HistoryController@show' - )->name('store.registration.collection-history')->where('registration', '^[0-9]+$'); - - // Removes a voucher from the current bundle - Route::delete( - '/registrations/{registration}/vouchers/{voucher}', - 'BundleController@removeVoucherFromCurrentBundle' - )->name('store.registration.voucher.delete' - )->where('registration', '^[0-9]+$' - )->where('voucher', '^[0-9]+$'); - - // Removes all the vouchers in the current bundle - Route::delete( - '/registrations/{registration}/vouchers', - 'BundleController@removeAllVouchersFromCurrentBundle' - )->name('store.registration.vouchers.delete')->where('registration', '^[0-9]+$'); - - // Add vouchers to bundle - Route::post( - '/registrations/{registration}/vouchers', - 'BundleController@addVouchersToCurrentBundle' - )->name('store.registration.vouchers.post')->where('registration', '^[0-9]+$'); - } - ); - - Route::group( - ['middleware' => 'can:export,App\CentreUser'], - function () { - - // ALL centres registrations as a summary spreadsheet - Route::get('/centres/registrations/summary', [ - 'as' => 'store.centres.registrations.summary', - 'uses' => 'CentreController@exportRegistrationsSummary', - ]); - - // ALL vouchers and extra details, in a format suitable for the MVL sheets. - // As this includes participant ID access, it has "can:export registrations". - Route::get('/vouchers/master-log', [ - 'as' => 'store.vouchers.mvl.export', - 'uses' => 'VoucherController@exportMasterVoucherLog', - ]); - - // Table of all historical voucher logs stored in our system. - Route::get('/vouchers/historical', [ - 'as' => 'store.vouchers.mvl.historical', - 'uses' => 'VoucherController@listVoucherLogs', - ]); - - // Downloads and decrypts voucher log file when the button at /vouchers/historical is pressed. - Route::get('/vouchers/download', [ - 'as' => 'store.vouchers.mvl.download', - 'uses' => 'VoucherController@downloadAndDecryptVoucherLogs', - ]); - - } - ); - - Route::group( - ['middleware' => 'can:viewRelevantCentre,centre'], - function () { - - // Print a Specific Centre's Registrations list - // Anyone who can view a centre can do this - Route::get('/centres/{centre}/registrations/collection', [ - 'as' => 'store.centre.registrations.collection', - 'uses' => 'CentreController@printCentreCollectionForm', - ])->where('centre', '^[0-9]+$'); - - // Export A specific centres' registrations summary spreadsheet. - // anyone who can view a centre AND download can do this. - Route::get('/centres/{centre}/registrations/summary', [ - 'as' => 'store.centre.registrations.summary', - 'uses' => 'CentreController@exportRegistrationsSummary', - ])->middleware(['can:download,App\CentreUser'])->where('centre', '^[0-9]+$'); - } - ); -}); \ No newline at end of file + ->where('token', '[0-9a-f]{64}'); +Route::post('password/reset', [ResetPasswordController::class, 'reset']); + +// Base redirect +Route::get('/', static fn () => redirect()->route('store.login'))->name('store.base'); + +// Authenticated routes +Route::middleware('auth:store')->group(function () { + + Route::get('dashboard', [DashboardController::class, 'index']) + ->name('store.dashboard'); + + Route::put('/session', [SessionController::class, 'update']) + ->name('store.session.put'); + + // Registrations + Route::get('/registrations', [RegistrationController::class, 'index']) + ->name('store.registration.index'); + Route::post('/registrations', [RegistrationController::class, 'store']) + ->name('store.registration.store'); + Route::get('/registrations/create', [RegistrationController::class, 'create']) + ->name('store.registration.create'); + Route::get('/registrations/print', [RegistrationController::class, 'printBatchIndividualFamilyForms']) + ->name('store.registrations.print'); + + // Specific registration actions (requires readOrUpdate policy) + Route::middleware('can:readOrUpdate,registration')->group(function () { + + Route::get('/registrations/{registration}/edit', [RegistrationController::class, 'edit']) + ->name('store.registration.edit') + ->whereNumber('registration'); + + Route::put('/registrations/{registration}', [RegistrationController::class, 'update']) + ->name('store.registration.update') + ->whereNumber('registration'); + + Route::get('/registrations/{registration}/view', [RegistrationController::class, 'view']) + ->name('store.registration.view') + ->whereNumber('registration'); + + Route::put('/registrations/{registration}/family', [FamilyController::class, 'update']) + ->name('store.registration.family') + ->whereNumber('registration'); + + Route::put('/registrations/{registration}/rejoin', [FamilyController::class, 'rejoin']) + ->name('store.registration.rejoin') + ->whereNumber('registration'); + + Route::get( + '/registrations/{registration}/print', + [RegistrationController::class, 'printOneIndividualFamilyForm'] + ) + ->name('store.registration.print') + ->whereNumber('registration'); + + Route::put('/registrations/{registration}/vouchers', [BundleController::class, 'update']) + ->name('store.registration.vouchers.put') + ->whereNumber('registration'); + + Route::get('/registrations/{registration}/voucher-manager', [BundleController::class, 'create']) + ->name('store.registration.voucher-manager') + ->whereNumber('registration'); + + Route::get('/registrations/{registration}/collection-history', [HistoryController::class, 'show']) + ->name('store.registration.collection-history') + ->whereNumber('registration'); + + Route::delete( + '/registrations/{registration}/vouchers/{voucher}', + [BundleController::class, 'removeVoucherFromCurrentBundle'] + ) + ->name('store.registration.voucher.delete') + ->whereNumber(['registration', 'voucher']); + + Route::delete( + '/registrations/{registration}/vouchers', + [BundleController::class, 'removeAllVouchersFromCurrentBundle'] + ) + ->name('store.registration.vouchers.delete') + ->whereNumber('registration'); + + Route::post('/registrations/{registration}/vouchers', [BundleController::class, 'addVouchersToCurrentBundle']) + ->name('store.registration.vouchers.post') + ->whereNumber('registration'); + }); + + // Export routes (requires export policy on CentreUser) + Route::middleware('can:export,App\CentreUser')->group(function (): void { + + Route::get('/centres/registrations/summary', [CentreController::class, 'exportRegistrationsSummary']) + ->name('store.centres.registrations.summary'); + + Route::get('/vouchers/master-log', [VoucherController::class, 'exportMasterVoucherLog']) + ->name('store.vouchers.mvl.export'); + + Route::get('/vouchers/historical', [VoucherController::class, 'listVoucherLogs']) + ->name('store.vouchers.mvl.historical'); + + Route::get('/vouchers/download', [VoucherController::class, 'downloadAndDecryptVoucherLogs']) + ->name('store.vouchers.mvl.download'); + }); + + // Centre-scoped routes (requires viewRelevantCentre policy) + Route::middleware('can:viewRelevantCentre,centre')->group(function (): void { + + Route::get('/centres/{centre}/registrations/collection', [CentreController::class, 'printCentreCollectionForm']) + ->name('store.centre.registrations.collection') + ->whereNumber('centre'); + + Route::get('/centres/{centre}/registrations/summary', [CentreController::class, 'exportRegistrationsSummary']) + ->name('store.centre.registrations.summary') + ->middleware('can:download,App\CentreUser') + ->whereNumber('centre'); + }); +}); From 9061abac3eefdd9d0ab418f0360b8f1b4fd245c1 Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 2 Apr 2026 10:33:00 +0100 Subject: [PATCH 041/168] refactor: use named routes in sidebar --- .../views/service/includes/sidebar.blade.php | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/resources/views/service/includes/sidebar.blade.php b/resources/views/service/includes/sidebar.blade.php index 7313478de..d162632e8 100644 --- a/resources/views/service/includes/sidebar.blade.php +++ b/resources/views/service/includes/sidebar.blade.php @@ -3,7 +3,7 @@ {{-- ===================== Dashboard ===================== --}} @@ -155,7 +161,7 @@ {{-- ===================== Developer Tools (non-production only) ===================== --}} - @unless(Config('app.url') === 'https://voucher-admin.alexandrarose.org.uk') + @can('take-developer-actions') - @endUnless + @endcan diff --git a/routes/data.php b/routes/data.php index acdd5ff93..06b09ac59 100644 --- a/routes/data.php +++ b/routes/data.php @@ -1,13 +1,14 @@ middleware('auth:admin') - ->group(function () { + ->group(static function () { // For now these routes are only available in dev and staging environments. Route::resource('vouchers', VoucherController::class)->only(['index', 'show']); - Route::resource('users', UserController::class)->only(['index']); - Route::resource('markets', MarketController::class)->only(['index']); - Route::resource('traders', TraderController::class)->only(['index']); + Route::get('users', [UserController::class, 'index'])->name('users.index'); + Route::get('markets', [MarketController::class, 'index'])->name('markets.index'); + Route::get('traders', [TraderController::class, 'index'])->name('traders.index'); + // Invokable + Route::get('families/contacts', FamilyContactsController::class)->name('families.contacts.download'); // Temporary route for demo only. Route::get('reset', static function () { - $process = new Process(['php', '../artisan', 'migrate:refresh', '--seed', '--force']); - $process->run(); + if (Gate::allows('take-developer-actions')) { + $process = new Process(['php', '../artisan', 'migrate:refresh', '--seed', '--force']); + $process->run(); - $process = new Process(['php', '../artisan', 'passport:install']); - $process->run(); + $process = new Process([ + 'php', + '../artisan', + 'passport:client', + '--password', + '--name="Rose Vouchers Password Grant Client"', + '--provider=users', + ]); + $process->run(); - $newSecret = DB::table('oauth_clients')->where('id', 2)->pluck('secret')[0]; - $envFilePath = base_path('.env'); - $oldSecret = env('PASSWORD_CLIENT_SECRET'); + $newSecret = DB::table('oauth_clients')->where('id', 1)->pluck('secret')[0]; + $envFilePath = base_path('.env'); + $oldSecret = env('PASSWORD_CLIENT_SECRET'); - file_put_contents($envFilePath, preg_replace( - "/^PASSWORD_CLIENT_SECRET={$oldSecret}/m", - "PASSWORD_CLIENT_SECRET={$newSecret}", - file_get_contents($envFilePath) - )); + file_put_contents($envFilePath, preg_replace( + "/^PASSWORD_CLIENT_SECRET={$oldSecret}/m", + "PASSWORD_CLIENT_SECRET={$newSecret}", + file_get_contents($envFilePath) + )); + return Redirect::route('admin.dashboard') + ->with('message', 'Reseeded @' . Carbon::now()); + } return Redirect::route('admin.dashboard') - ->with('message', 'Reseeded @' . Carbon::now()); + ->with('error', 'Action Denied'); })->name('reset'); }); diff --git a/tests/Feature/Service/FamilyContactsControllerTest.php b/tests/Feature/Service/FamilyContactsControllerTest.php new file mode 100644 index 000000000..7b25d1c1f --- /dev/null +++ b/tests/Feature/Service/FamilyContactsControllerTest.php @@ -0,0 +1,191 @@ +admin = factory(AdminUser::class)->create(); + } + + protected function tearDown(): void + { + Carbon::setTestNow(); + parent::tearDown(); + } + + /** + * Creates a fully-related Carer — the happy-path fixture. + * Override any attribute by passing it in $carerAttributes. + */ + private function createQualifyingCarer(array $carerAttributes = []): Carer + { + $sponsor = factory(Sponsor::class)->create(['name' => 'Test Area']); + $centre = factory(Centre::class)->create([ + 'name' => 'Test Centre', + 'sponsor_id' => $sponsor->id, + ]); + $family = factory(Family::class)->create(['initial_centre_id' => $centre->id]); + + return factory(Carer::class)->create(array_merge([ + 'family_id' => $family->id, + 'name' => 'Jane Doe', + 'emailsecret' => 'jane@example.com', + 'telnosecret' => '07700900000', + ], $carerAttributes)); + } + + private function makeRequest(): TestResponse + { + return $this->actingAs($this->admin, 'admin') + ->get(route('data.families.contacts.download')); + } + + public function testReturns200(): void + { + $this->makeRequest()->assertOk(); + } + + public function testContentTypeIsCsv(): void + { + $this->makeRequest()->assertHeaderContains('Content-Type', 'text/csv'); + } + + public function testCacheControlIsNoCache(): void + { + $this->makeRequest()->assertHeaderContains('Cache-Control', 'no-cache'); + } + + public function testFilenameContainsFormattedTimestamp(): void + { + $disposition = $this->makeRequest()->headers->get('Content-Disposition'); + + $this->assertStringContainsString('carers-export_20240315093000.csv', $disposition); + } + + public function testFilenameChangesWithCurrentTime(): void + { + Carbon::setTestNow('2099-12-31 23:59:59'); + + $disposition = $this->makeRequest()->headers->get('Content-Disposition'); + + $this->assertStringContainsString('carers-export_20991231235959.csv', $disposition); + } + + public function testFirstRowIsHeader(): void + { + $rows = $this->parseCsv($this->makeRequest()->streamedContent()); + + $this->assertSame(['Rvid', 'Name', 'Email', 'Telno', 'Centre', 'Area'], $rows[0]); + } + + /** @return array> */ + private function parseCsv(string $content): array + { + $lines = array_filter(explode("\n", trim($content))); + return array_map('str_getcsv', array_values($lines)); + } + + public function testEmptyResultSetReturnsHeaderOnly(): void + { + $rows = $this->parseCsv($this->makeRequest()->streamedContent()); + + $this->assertCount(1, $rows); + } + + public function testExcludesCarerWithNullEmailsecret(): void + { + $this->createQualifyingCarer(['emailsecret' => null]); + + $rows = $this->parseCsv($this->makeRequest()->streamedContent()); + + $this->assertCount(1, $rows, 'Expected header row only — carer should be excluded.'); + } + + public function testExcludesCarerWithNullTelnosecret(): void + { + $this->createQualifyingCarer(['telnosecret' => null]); + + $rows = $this->parseCsv($this->makeRequest()->streamedContent()); + + $this->assertCount(1, $rows, 'Expected header row only — carer should be excluded.'); + } + + public function testExcludesCarerWithBothSecretsNull(): void + { + $this->createQualifyingCarer(['emailsecret' => null, 'telnosecret' => null]); + + $rows = $this->parseCsv($this->makeRequest()->streamedContent()); + + $this->assertCount(1, $rows); + } + + public function testIncludesCarerWithBothSecretsPresent(): void + { + $this->createQualifyingCarer(); + + $rows = $this->parseCsv($this->makeRequest()->streamedContent()); + + $this->assertCount(2, $rows, 'Expected header row + 1 data row.'); + } + + public function testDataRowMapsAllColumnsCorrectly(): void + { + $carer = $this->createQualifyingCarer([ + 'name' => 'Jane Doe', + 'emailsecret' => 'jane@example.com', + 'telnosecret' => '07700900000', + ]); + + $rows = $this->parseCsv($this->makeRequest()->streamedContent()); + $dataRow = $rows[1]; + + $this->assertSame((string)$carer->family->Rvid, $dataRow[0], 'Rvid'); + $this->assertSame('Jane Doe', $dataRow[1], 'Name'); + $this->assertSame('jane@example.com', $dataRow[2], 'Email'); + $this->assertSame('07700900000', $dataRow[3], 'Telno'); + $this->assertSame('Test Centre', $dataRow[4], 'Centre'); + $this->assertSame('Test Area', $dataRow[5], 'Area'); + } + + public function testMultipleQualifyingCarersAllAppear(): void + { + $this->createQualifyingCarer(['name' => 'Carer One']); + $this->createQualifyingCarer(['name' => 'Carer Two']); + $this->createQualifyingCarer(['name' => 'Carer Three']); + + $rows = $this->parseCsv($this->makeRequest()->streamedContent()); + + $this->assertCount(4, $rows, 'Expected header row + 3 data rows.'); + } + + public function testQualifyingAndNonQualifyingCarersMixed(): void + { + $this->createQualifyingCarer(['name' => 'Included']); + $this->createQualifyingCarer(['name' => 'Excluded — no email', 'emailsecret' => null]); + $this->createQualifyingCarer(['name' => 'Excluded — no telno', 'telnosecret' => null]); + + $rows = $this->parseCsv($this->makeRequest()->streamedContent()); + $dataRows = array_slice($rows, 1); + + $this->assertCount(1, $dataRows); + $this->assertSame('Included', $dataRows[0][1]); + } +} From db18434899c7892f455a668fda6bc7d2a93c9c02 Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 2 Apr 2026 16:19:03 +0100 Subject: [PATCH 043/168] fix: data route tests --- routes/data.php | 17 +-- tests/Feature/Service/AdminResetTest.php | 186 ++++++++++++++++------- 2 files changed, 138 insertions(+), 65 deletions(-) diff --git a/routes/data.php b/routes/data.php index 06b09ac59..74d9c37c6 100644 --- a/routes/data.php +++ b/routes/data.php @@ -8,7 +8,6 @@ use Carbon\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Redirect; -use Symfony\Component\Process\Process; /* |-------------------------------------------------------------------------- @@ -34,20 +33,16 @@ // Temporary route for demo only. Route::get('reset', static function () { if (Gate::allows('take-developer-actions')) { - $process = new Process(['php', '../artisan', 'migrate:refresh', '--seed', '--force']); - $process->run(); + Artisan::call('migrate:refresh', ['--seed' => true, '--force' => true]); - $process = new Process([ - 'php', - '../artisan', - 'passport:client', - '--password', - '--name="Rose Vouchers Password Grant Client"', - '--provider=users', + Artisan::call('passport:client', [ + '--password' => true, + '--name' => 'Rose Vouchers Password Grant Client', + '--provider' => 'users', ]); - $process->run(); $newSecret = DB::table('oauth_clients')->where('id', 1)->pluck('secret')[0]; + $envFilePath = base_path('.env'); $oldSecret = env('PASSWORD_CLIENT_SECRET'); diff --git a/tests/Feature/Service/AdminResetTest.php b/tests/Feature/Service/AdminResetTest.php index d2fb3f463..adff658c6 100644 --- a/tests/Feature/Service/AdminResetTest.php +++ b/tests/Feature/Service/AdminResetTest.php @@ -1,74 +1,152 @@ adminUser = factory(AdminUser::class)->create(); + $this->admin = factory(AdminUser::class)->create(); } - public function testResetRoute(): void + private function makeRequest(): TestResponse { - // Mock the Process command - $mockProcess = Mockery::mock(Process::class); - $this->app->instance(Process::class, $mockProcess); - DB::table('oauth_clients')->insert( - array( - 'id' => 2, - 'user_id' => 555, - 'name' => "0", - 'secret' => "0", - 'provider' => "0", - 'redirect' => "0", - 'personal_access_client' => 3, - 'password_client' => 4, - 'revoked' => 5, - 'created_at' => "0", - 'updated_at' => "0", - ) - ); - // Mock DB - DOES NOT WORK. I think the Process call spawns a new thread - // $mockDb = Mockery::mock(DatabaseManager::class, ); - // $mockDb->shouldReceive('table->where->pluck') - // ->once() - // ->with("secret") - // ->andReturn([ - // [ - // 'id' => 1, - // 'userId' => 555, - // 'name' => "0", - // 'secret' => "0", - // 'provider' => "0", - // 'redirect' => "0", - // 'personal_access_client' => 3, - // 'password_client' => 4, - // 'revoked' => 5, - // 'created_at' => "0", - // 'updated_at' => "0", - // ] - // ]); - // $cls = get_class(DB::getFacadeRoot()); - - // Run the test - $this->actingAs($this->adminUser, 'admin') - ->get(route('data.reset')) - ->assertResponseStatus(302); + return $this->actingAs($this->admin, 'admin') + ->get(route('data.reset')); + } + + /** + * Mocks the DB::table('oauth_clients') chain used to retrieve the new + * passport secret after the seed. Avoids a dependency on the oauth_clients + * table existing in the test schema. + */ + private function mockOauthClientQuery(string $secret = 'new-test-secret'): void + { + $builder = Mockery::mock(); + $builder->shouldReceive('where')->with('id', 1)->andReturnSelf(); + $builder->shouldReceive('pluck')->with('secret')->andReturn(collect([$secret])); + + DB::shouldReceive('table')->with('oauth_clients')->andReturn($builder); + } + + public function testRedirectsUnauthenticatedUser(): void + { + $response = $this->get(route('data.reset')); + + $response->assertRedirect(); + } + + public function testUnauthenticatedRequestDoesNotReachGateCheck(): void + { + Gate::shouldReceive('allows')->never(); + + $this->get(route('data.reset')); + } + + public function testRedirectsToDashboardWhenGateDenies(): void + { + Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(false); + + $this->makeRequest()->assertRedirect(route('admin.dashboard')); + } + + public function testFlashesErrorWhenGateDenies(): void + { + Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(false); + + $this->makeRequest()->assertSessionHas('error', 'Action Denied'); + } + + public function testDoesNotFlashSuccessMessageWhenGateDenies(): void + { + Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(false); + + $this->makeRequest()->assertSessionMissing('message'); + } + + public function testRedirectsToDashboardWhenGateAllows(): void + { + Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); + $this->mockOauthClientQuery(); + + $this->makeRequest()->assertRedirect(route('admin.dashboard')); + } + + public function testFlashesSuccessMessageWhenGateAllows(): void + { + Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); + $this->mockOauthClientQuery(); + + $this->makeRequest()->assertSessionHas('message'); + } + + public function testSuccessMessageContainsReseededText(): void + { + Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); + $this->mockOauthClientQuery(); + + $message = $this->makeRequest()->getSession()->get('message'); + + $this->assertStringContainsString('Reseeded', $message); + } + + public function testDoesNotFlashErrorMessageWhenGateAllows(): void + { + Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); + $this->mockOauthClientQuery(); + + $this->makeRequest()->assertSessionMissing('error'); + } + + public function testRunsMigrateRefreshWhenGateAllows(): void + { + Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); + $this->mockOauthClientQuery(); + Artisan::shouldReceive('call') + ->once() + ->with('migrate:refresh', ['--seed' => true, '--force' => true]); + Artisan::shouldReceive('call') + ->once() + ->with('passport:client', Mockery::any()); + + $this->makeRequest(); + } + + public function testCreatesPassportClientWhenGateAllows(): void + { + Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); + $this->mockOauthClientQuery(); + Artisan::shouldReceive('call')->once()->with('migrate:refresh', Mockery::any()); + Artisan::shouldReceive('call') + ->once() + ->with('passport:client', [ + '--password' => true, + '--name' => 'Rose Vouchers Password Grant Client', + '--provider' => 'users', + ]); + + $this->makeRequest(); + } + + public function testArtisanIsNotCalledWhenGateDenies(): void + { + Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(false); + Artisan::shouldReceive('call')->never(); + + $this->makeRequest(); } } From a556fdbf9863e2bda5275354f05754ccb2c1578b Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 2 Apr 2026 17:01:06 +0100 Subject: [PATCH 044/168] fix: danagerous env writing! use a mockable thing writer service --- app/Providers/AppServiceProvider.php | 5 +- app/Services/EnvWriter.php | 21 +++ routes/data.php | 10 +- tests/Feature/Service/AdminResetTest.php | 81 +++++++++--- tests/Services/EnvWriterTest.php | 160 +++++++++++++++++++++++ 5 files changed, 248 insertions(+), 29 deletions(-) create mode 100644 app/Services/EnvWriter.php create mode 100644 tests/Services/EnvWriterTest.php diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index cbd2089fc..e31e7738e 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use App\Services\EnvWriter; use App\View\Composers\PaymentsComposer; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\Gate; @@ -47,6 +48,8 @@ public function boot(): void */ public function register(): void { - // manual registration of non-auto-discovered packages + $this->app->bind(EnvWriter::class, function () { + return new EnvWriter(base_path('.env')); + }); } } diff --git a/app/Services/EnvWriter.php b/app/Services/EnvWriter.php new file mode 100644 index 000000000..c3b5c96d1 --- /dev/null +++ b/app/Services/EnvWriter.php @@ -0,0 +1,21 @@ +envPath); + + file_put_contents($this->envPath, preg_replace( + "/^{$key}=.*/m", + "{$key}={$newValue}", + $contents + )); + } +} diff --git a/routes/data.php b/routes/data.php index 74d9c37c6..84e1a8914 100644 --- a/routes/data.php +++ b/routes/data.php @@ -5,6 +5,7 @@ use App\Http\Controllers\Service\Data\TraderController; use App\Http\Controllers\Service\Data\UserController; use App\Http\Controllers\Service\Data\VoucherController; +use App\Services\EnvWriter; use Carbon\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Redirect; @@ -43,14 +44,7 @@ $newSecret = DB::table('oauth_clients')->where('id', 1)->pluck('secret')[0]; - $envFilePath = base_path('.env'); - $oldSecret = env('PASSWORD_CLIENT_SECRET'); - - file_put_contents($envFilePath, preg_replace( - "/^PASSWORD_CLIENT_SECRET={$oldSecret}/m", - "PASSWORD_CLIENT_SECRET={$newSecret}", - file_get_contents($envFilePath) - )); + app(EnvWriter::class)->updateKey('PASSWORD_CLIENT_SECRET', $newSecret); return Redirect::route('admin.dashboard') ->with('message', 'Reseeded @' . Carbon::now()); diff --git a/tests/Feature/Service/AdminResetTest.php b/tests/Feature/Service/AdminResetTest.php index adff658c6..59f7fab3a 100644 --- a/tests/Feature/Service/AdminResetTest.php +++ b/tests/Feature/Service/AdminResetTest.php @@ -3,6 +3,7 @@ namespace Feature\Service; use App\AdminUser; +use App\Services\EnvWriter; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\DB; @@ -20,9 +21,18 @@ class AdminResetTest extends TestCase protected function setUp(): void { parent::setUp(); + // don't write to our env file! + $this->mock(EnvWriter::class) + ->shouldReceive('updateKey') + ->andReturnNull(); + $this->admin = factory(AdminUser::class)->create(); } + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + private function makeRequest(): TestResponse { return $this->actingAs($this->admin, 'admin') @@ -30,9 +40,18 @@ private function makeRequest(): TestResponse } /** - * Mocks the DB::table('oauth_clients') chain used to retrieve the new - * passport secret after the seed. Avoids a dependency on the oauth_clients - * table existing in the test schema. + * Prevents Artisan commands from actually executing in gate-allowed tests + * that aren't specifically asserting on Artisan behaviour. + * Must be called before mockOauthClientQuery() to avoid DB facade conflicts. + */ + private function mockArtisan(): void + { + Artisan::shouldReceive('call')->andReturn(0); + } + + /** + * Mocks the DB::table('oauth_clients') chain. Call this after mockArtisan() + * so the DB facade is only locked down once Artisan is already stubbed out. */ private function mockOauthClientQuery(string $secret = 'new-test-secret'): void { @@ -43,11 +62,13 @@ private function mockOauthClientQuery(string $secret = 'new-test-secret'): void DB::shouldReceive('table')->with('oauth_clients')->andReturn($builder); } + // ------------------------------------------------------------------------- + // Authentication + // ------------------------------------------------------------------------- + public function testRedirectsUnauthenticatedUser(): void { - $response = $this->get(route('data.reset')); - - $response->assertRedirect(); + $this->get(route('data.reset'))->assertRedirect(); } public function testUnauthenticatedRequestDoesNotReachGateCheck(): void @@ -57,6 +78,10 @@ public function testUnauthenticatedRequestDoesNotReachGateCheck(): void $this->get(route('data.reset')); } + // ------------------------------------------------------------------------- + // Gate denied + // ------------------------------------------------------------------------- + public function testRedirectsToDashboardWhenGateDenies(): void { Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(false); @@ -78,9 +103,22 @@ public function testDoesNotFlashSuccessMessageWhenGateDenies(): void $this->makeRequest()->assertSessionMissing('message'); } + public function testArtisanIsNotCalledWhenGateDenies(): void + { + Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(false); + Artisan::shouldReceive('call')->never(); + + $this->makeRequest(); + } + + // ------------------------------------------------------------------------- + // Gate allowed + // ------------------------------------------------------------------------- + public function testRedirectsToDashboardWhenGateAllows(): void { Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); + $this->mockArtisan(); $this->mockOauthClientQuery(); $this->makeRequest()->assertRedirect(route('admin.dashboard')); @@ -89,6 +127,7 @@ public function testRedirectsToDashboardWhenGateAllows(): void public function testFlashesSuccessMessageWhenGateAllows(): void { Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); + $this->mockArtisan(); $this->mockOauthClientQuery(); $this->makeRequest()->assertSessionHas('message'); @@ -97,16 +136,22 @@ public function testFlashesSuccessMessageWhenGateAllows(): void public function testSuccessMessageContainsReseededText(): void { Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); + $this->mockArtisan(); $this->mockOauthClientQuery(); - $message = $this->makeRequest()->getSession()->get('message'); - - $this->assertStringContainsString('Reseeded', $message); + // assertSessionHas with a closure replaces the missing getSession() proxy + $this->makeRequest()->assertSessionHas( + 'message', + function (string $value) { + return str_contains($value, 'Reseeded'); + } + ); } public function testDoesNotFlashErrorMessageWhenGateAllows(): void { Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); + $this->mockArtisan(); $this->mockOauthClientQuery(); $this->makeRequest()->assertSessionMissing('error'); @@ -115,13 +160,14 @@ public function testDoesNotFlashErrorMessageWhenGateAllows(): void public function testRunsMigrateRefreshWhenGateAllows(): void { Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); - $this->mockOauthClientQuery(); + // Fine-grained expectations — set up Artisan before DB Artisan::shouldReceive('call') ->once() ->with('migrate:refresh', ['--seed' => true, '--force' => true]); Artisan::shouldReceive('call') ->once() ->with('passport:client', Mockery::any()); + $this->mockOauthClientQuery(); $this->makeRequest(); } @@ -129,8 +175,10 @@ public function testRunsMigrateRefreshWhenGateAllows(): void public function testCreatesPassportClientWhenGateAllows(): void { Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); - $this->mockOauthClientQuery(); - Artisan::shouldReceive('call')->once()->with('migrate:refresh', Mockery::any()); + // Fine-grained expectations — set up Artisan before DB + Artisan::shouldReceive('call') + ->once() + ->with('migrate:refresh', Mockery::any()); Artisan::shouldReceive('call') ->once() ->with('passport:client', [ @@ -138,14 +186,7 @@ public function testCreatesPassportClientWhenGateAllows(): void '--name' => 'Rose Vouchers Password Grant Client', '--provider' => 'users', ]); - - $this->makeRequest(); - } - - public function testArtisanIsNotCalledWhenGateDenies(): void - { - Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(false); - Artisan::shouldReceive('call')->never(); + $this->mockOauthClientQuery(); $this->makeRequest(); } diff --git a/tests/Services/EnvWriterTest.php b/tests/Services/EnvWriterTest.php new file mode 100644 index 000000000..a070e614e --- /dev/null +++ b/tests/Services/EnvWriterTest.php @@ -0,0 +1,160 @@ +envPath = tempnam(sys_get_temp_dir(), 'envwriter_test_'); + } + + protected function tearDown(): void + { + if (file_exists($this->envPath)) { + unlink($this->envPath); + } + parent::tearDown(); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private function makeWriter(): EnvWriter + { + return new EnvWriter($this->envPath); + } + + private function writeEnv(string $contents): void + { + file_put_contents($this->envPath, $contents); + } + + private function readEnv(): string + { + return file_get_contents($this->envPath); + } + + // ------------------------------------------------------------------------- + // Tests + // ------------------------------------------------------------------------- + + public function testUpdatesMatchingKey(): void + { + $this->writeEnv("PASSWORD_CLIENT_SECRET=old-secret\n"); + + $this->makeWriter()->updateKey('PASSWORD_CLIENT_SECRET', 'new-secret'); + + $this->assertStringContainsString('PASSWORD_CLIENT_SECRET=new-secret', $this->readEnv()); + } + + public function testRemovesOldValueFromFile(): void + { + $this->writeEnv("PASSWORD_CLIENT_SECRET=old-secret\n"); + + $this->makeWriter()->updateKey('PASSWORD_CLIENT_SECRET', 'new-secret'); + + $this->assertStringNotContainsString('old-secret', $this->readEnv()); + } + + public function testLeavesOtherKeysUnchanged(): void + { + $this->writeEnv( + "APP_NAME=MyApp\n" . + "PASSWORD_CLIENT_SECRET=old-secret\n" . + "APP_ENV=local\n" + ); + + $this->makeWriter()->updateKey('PASSWORD_CLIENT_SECRET', 'new-secret'); + + $env = $this->readEnv(); + $this->assertStringContainsString('APP_NAME=MyApp', $env); + $this->assertStringContainsString('APP_ENV=local', $env); + } + + public function testLeavesFileUnchangedWhenKeyNotPresent(): void + { + $original = "APP_NAME=MyApp\nAPP_ENV=local\n"; + $this->writeEnv($original); + + $this->makeWriter()->updateKey('PASSWORD_CLIENT_SECRET', 'new-secret'); + + $this->assertSame($original, $this->readEnv()); + } + + public function testUpdatesKeyWithEmptyCurrentValue(): void + { + $this->writeEnv("PASSWORD_CLIENT_SECRET=\n"); + + $this->makeWriter()->updateKey('PASSWORD_CLIENT_SECRET', 'new-secret'); + + $this->assertStringContainsString('PASSWORD_CLIENT_SECRET=new-secret', $this->readEnv()); + } + + public function testUpdatesKeyOnFirstLine(): void + { + $this->writeEnv("PASSWORD_CLIENT_SECRET=old-secret\nAPP_NAME=MyApp\n"); + + $this->makeWriter()->updateKey('PASSWORD_CLIENT_SECRET', 'new-secret'); + + $this->assertStringContainsString('PASSWORD_CLIENT_SECRET=new-secret', $this->readEnv()); + } + + public function testUpdatesKeyOnLastLineWithNoTrailingNewline(): void + { + $this->writeEnv("APP_NAME=MyApp\nPASSWORD_CLIENT_SECRET=old-secret"); + + $this->makeWriter()->updateKey('PASSWORD_CLIENT_SECRET', 'new-secret'); + + $this->assertStringContainsString('PASSWORD_CLIENT_SECRET=new-secret', $this->readEnv()); + } + + public function testDoesNotMatchPartialKeyName(): void + { + $this->writeEnv("OTHER_PASSWORD_CLIENT_SECRET=old-secret\n"); + + $this->makeWriter()->updateKey('PASSWORD_CLIENT_SECRET', 'new-secret'); + + // Partial match should not have been updated + $this->assertStringContainsString('OTHER_PASSWORD_CLIENT_SECRET=old-secret', $this->readEnv()); + $this->assertStringNotContainsString('PASSWORD_CLIENT_SECRET=new-secret', $this->readEnv()); + } + + public function testWritesCorrectlyToRealisticEnvFile(): void + { + $this->writeEnv( + "APP_NAME=ARCVService\n" . + "APP_ENV=testing\n" . + "APP_KEY=base64:abc123\n" . + "DB_CONNECTION=mysql\n" . + "PASSWORD_CLIENT_SECRET=old-secret\n" . + "SOME_OTHER_KEY=value\n" + ); + + $this->makeWriter()->updateKey('PASSWORD_CLIENT_SECRET', 'brand-new-secret'); + + $env = $this->readEnv(); + $this->assertStringContainsString('PASSWORD_CLIENT_SECRET=brand-new-secret', $env); + $this->assertStringContainsString('APP_NAME=ARCVService', $env); + $this->assertStringContainsString('APP_KEY=base64:abc123', $env); + $this->assertStringContainsString('DB_CONNECTION=mysql', $env); + $this->assertStringContainsString('SOME_OTHER_KEY=value', $env); + $this->assertStringNotContainsString('old-secret', $env); + } + + public function testTempFileIsCleanedUpBetweenTests(): void + { + $this->assertFileExists($this->envPath); + } +} From 5c5ea5ca20b91ad5e0115752bd6d7ad786157752 Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 2 Apr 2026 17:35:59 +0100 Subject: [PATCH 045/168] fix: tests, by pulling things out to being jobs --- app/Jobs/ResetDemoEnvironment.php | 34 ++++++ routes/data.php | 18 +-- tests/Feature/Service/AdminResetTest.php | 103 +++------------- tests/Unit/Jobs/ResetDemoEnvironmentTest.php | 118 +++++++++++++++++++ 4 files changed, 173 insertions(+), 100 deletions(-) create mode 100644 app/Jobs/ResetDemoEnvironment.php create mode 100644 tests/Unit/Jobs/ResetDemoEnvironmentTest.php diff --git a/app/Jobs/ResetDemoEnvironment.php b/app/Jobs/ResetDemoEnvironment.php new file mode 100644 index 000000000..eb91dbb8b --- /dev/null +++ b/app/Jobs/ResetDemoEnvironment.php @@ -0,0 +1,34 @@ + true, '--force' => true]); + + Artisan::call('passport:client', [ + '--password' => true, + '--name' => 'Rose Vouchers Password Grant Client', + '--provider' => 'users', + ]); + + $newSecret = DB::table('oauth_clients')->where('id', 1)->pluck('secret')[0]; + $envWriter->updateKey('PASSWORD_CLIENT_SECRET', $newSecret); + } +} diff --git a/routes/data.php b/routes/data.php index 84e1a8914..8e6bf4763 100644 --- a/routes/data.php +++ b/routes/data.php @@ -5,9 +5,7 @@ use App\Http\Controllers\Service\Data\TraderController; use App\Http\Controllers\Service\Data\UserController; use App\Http\Controllers\Service\Data\VoucherController; -use App\Services\EnvWriter; -use Carbon\Carbon; -use Illuminate\Support\Facades\DB; +use App\Jobs\ResetDemoEnvironment; use Illuminate\Support\Facades\Redirect; /* @@ -34,20 +32,10 @@ // Temporary route for demo only. Route::get('reset', static function () { if (Gate::allows('take-developer-actions')) { - Artisan::call('migrate:refresh', ['--seed' => true, '--force' => true]); - - Artisan::call('passport:client', [ - '--password' => true, - '--name' => 'Rose Vouchers Password Grant Client', - '--provider' => 'users', - ]); - - $newSecret = DB::table('oauth_clients')->where('id', 1)->pluck('secret')[0]; - - app(EnvWriter::class)->updateKey('PASSWORD_CLIENT_SECRET', $newSecret); + ResetDemoEnvironment::dispatch(); return Redirect::route('admin.dashboard') - ->with('message', 'Reseeded @' . Carbon::now()); + ->with('message', 'Reset queued'); } return Redirect::route('admin.dashboard') ->with('error', 'Action Denied'); diff --git a/tests/Feature/Service/AdminResetTest.php b/tests/Feature/Service/AdminResetTest.php index 59f7fab3a..256f62116 100644 --- a/tests/Feature/Service/AdminResetTest.php +++ b/tests/Feature/Service/AdminResetTest.php @@ -3,13 +3,11 @@ namespace Feature\Service; use App\AdminUser; -use App\Services\EnvWriter; +use App\Jobs\ResetDemoEnvironment; use Illuminate\Foundation\Testing\RefreshDatabase; -use Illuminate\Support\Facades\Artisan; -use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Gate; use Illuminate\Testing\TestResponse; -use Mockery; use Tests\TestCase; class AdminResetTest extends TestCase @@ -21,11 +19,7 @@ class AdminResetTest extends TestCase protected function setUp(): void { parent::setUp(); - // don't write to our env file! - $this->mock(EnvWriter::class) - ->shouldReceive('updateKey') - ->andReturnNull(); - + Bus::fake(); $this->admin = factory(AdminUser::class)->create(); } @@ -39,29 +33,6 @@ private function makeRequest(): TestResponse ->get(route('data.reset')); } - /** - * Prevents Artisan commands from actually executing in gate-allowed tests - * that aren't specifically asserting on Artisan behaviour. - * Must be called before mockOauthClientQuery() to avoid DB facade conflicts. - */ - private function mockArtisan(): void - { - Artisan::shouldReceive('call')->andReturn(0); - } - - /** - * Mocks the DB::table('oauth_clients') chain. Call this after mockArtisan() - * so the DB facade is only locked down once Artisan is already stubbed out. - */ - private function mockOauthClientQuery(string $secret = 'new-test-secret'): void - { - $builder = Mockery::mock(); - $builder->shouldReceive('where')->with('id', 1)->andReturnSelf(); - $builder->shouldReceive('pluck')->with('secret')->andReturn(collect([$secret])); - - DB::shouldReceive('table')->with('oauth_clients')->andReturn($builder); - } - // ------------------------------------------------------------------------- // Authentication // ------------------------------------------------------------------------- @@ -78,6 +49,13 @@ public function testUnauthenticatedRequestDoesNotReachGateCheck(): void $this->get(route('data.reset')); } + public function testUnauthenticatedRequestDoesNotDispatchJob(): void + { + $this->get(route('data.reset')); + + Bus::assertNothingDispatched(); + } + // ------------------------------------------------------------------------- // Gate denied // ------------------------------------------------------------------------- @@ -103,12 +81,13 @@ public function testDoesNotFlashSuccessMessageWhenGateDenies(): void $this->makeRequest()->assertSessionMissing('message'); } - public function testArtisanIsNotCalledWhenGateDenies(): void + public function testDoesNotDispatchJobWhenGateDenies(): void { Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(false); - Artisan::shouldReceive('call')->never(); $this->makeRequest(); + + Bus::assertNothingDispatched(); } // ------------------------------------------------------------------------- @@ -118,76 +97,30 @@ public function testArtisanIsNotCalledWhenGateDenies(): void public function testRedirectsToDashboardWhenGateAllows(): void { Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); - $this->mockArtisan(); - $this->mockOauthClientQuery(); $this->makeRequest()->assertRedirect(route('admin.dashboard')); } - public function testFlashesSuccessMessageWhenGateAllows(): void + public function testFlashesQueuedMessageWhenGateAllows(): void { Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); - $this->mockArtisan(); - $this->mockOauthClientQuery(); - - $this->makeRequest()->assertSessionHas('message'); - } - public function testSuccessMessageContainsReseededText(): void - { - Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); - $this->mockArtisan(); - $this->mockOauthClientQuery(); - - // assertSessionHas with a closure replaces the missing getSession() proxy - $this->makeRequest()->assertSessionHas( - 'message', - function (string $value) { - return str_contains($value, 'Reseeded'); - } - ); + $this->makeRequest()->assertSessionHas('message', 'Reset queued'); } - public function testDoesNotFlashErrorMessageWhenGateAllows(): void + public function testDoesNotFlashErrorWhenGateAllows(): void { Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); - $this->mockArtisan(); - $this->mockOauthClientQuery(); $this->makeRequest()->assertSessionMissing('error'); } - public function testRunsMigrateRefreshWhenGateAllows(): void + public function testDispatchesResetJobWhenGateAllows(): void { Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); - // Fine-grained expectations — set up Artisan before DB - Artisan::shouldReceive('call') - ->once() - ->with('migrate:refresh', ['--seed' => true, '--force' => true]); - Artisan::shouldReceive('call') - ->once() - ->with('passport:client', Mockery::any()); - $this->mockOauthClientQuery(); $this->makeRequest(); - } - public function testCreatesPassportClientWhenGateAllows(): void - { - Gate::shouldReceive('allows')->with('take-developer-actions')->andReturn(true); - // Fine-grained expectations — set up Artisan before DB - Artisan::shouldReceive('call') - ->once() - ->with('migrate:refresh', Mockery::any()); - Artisan::shouldReceive('call') - ->once() - ->with('passport:client', [ - '--password' => true, - '--name' => 'Rose Vouchers Password Grant Client', - '--provider' => 'users', - ]); - $this->mockOauthClientQuery(); - - $this->makeRequest(); + Bus::assertDispatched(ResetDemoEnvironment::class); } } diff --git a/tests/Unit/Jobs/ResetDemoEnvironmentTest.php b/tests/Unit/Jobs/ResetDemoEnvironmentTest.php new file mode 100644 index 000000000..59dc3d09e --- /dev/null +++ b/tests/Unit/Jobs/ResetDemoEnvironmentTest.php @@ -0,0 +1,118 @@ +shouldReceive('where')->with('id', 1)->andReturnSelf(); + $builder->shouldReceive('pluck')->with('secret')->andReturn(collect([$secret])); + + DB::shouldReceive('table')->with('oauth_clients')->andReturn($builder); + } + + private function mockEnvWriter(): MockInterface + { + return $this->mock(EnvWriter::class); + } + + private function dispatchJob(): void + { + app(ResetDemoEnvironment::class)->handle(app(EnvWriter::class)); + } + + // ------------------------------------------------------------------------- + // Artisan commands + // ------------------------------------------------------------------------- + + public function testRunsMigrateRefresh(): void + { + Artisan::shouldReceive('call') + ->once() + ->with('migrate:refresh', ['--seed' => true, '--force' => true]); + Artisan::shouldReceive('call') + ->once() + ->with('passport:client', Mockery::any()); + $this->mockEnvWriter()->shouldReceive('updateKey')->andReturnNull(); + $this->mockOauthClientQuery(); + + $this->dispatchJob(); + } + + public function testCreatesPassportClientWithCorrectArguments(): void + { + Artisan::shouldReceive('call')->once()->with('migrate:refresh', Mockery::any()); + Artisan::shouldReceive('call') + ->once() + ->with('passport:client', [ + '--password' => true, + '--name' => 'Rose Vouchers Password Grant Client', + '--provider' => 'users', + ]); + $this->mockEnvWriter()->shouldReceive('updateKey')->andReturnNull(); + $this->mockOauthClientQuery(); + + $this->dispatchJob(); + } + + // ------------------------------------------------------------------------- + // Secret retrieval + // ------------------------------------------------------------------------- + + public function testQueriesOauthClientsForNewSecret(): void + { + Artisan::shouldReceive('call')->andReturn(0); + $this->mockEnvWriter()->shouldReceive('updateKey')->andReturnNull(); + + $builder = Mockery::mock(); + $builder->shouldReceive('where')->once()->with('id', 1)->andReturnSelf(); + $builder->shouldReceive('pluck')->once()->with('secret')->andReturn(collect(['expected-secret'])); + DB::shouldReceive('table')->once()->with('oauth_clients')->andReturn($builder); + + $this->dispatchJob(); + } + + // ------------------------------------------------------------------------- + // EnvWriter + // ------------------------------------------------------------------------- + + public function testWritesNewSecretToEnvFile(): void + { + Artisan::shouldReceive('call')->andReturn(0); + $this->mockOauthClientQuery('brand-new-secret'); + + $this->mockEnvWriter() + ->shouldReceive('updateKey') + ->once() + ->with('PASSWORD_CLIENT_SECRET', 'brand-new-secret'); + + $this->dispatchJob(); + } + + public function testWritesToCorrectEnvKey(): void + { + Artisan::shouldReceive('call')->andReturn(0); + $this->mockOauthClientQuery(); + + $this->mockEnvWriter() + ->shouldReceive('updateKey') + ->once() + ->with('PASSWORD_CLIENT_SECRET', Mockery::any()); + + $this->dispatchJob(); + } +} From 83e58b6ada3f83afb7009184d66ccf1222ef4fcf Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 2 Apr 2026 17:53:21 +0100 Subject: [PATCH 046/168] fix: really fixed this time. --- app/Jobs/ResetDemoEnvironment.php | 19 +++--- tests/Unit/Jobs/ResetDemoEnvironmentTest.php | 69 +++++++++----------- 2 files changed, 40 insertions(+), 48 deletions(-) diff --git a/app/Jobs/ResetDemoEnvironment.php b/app/Jobs/ResetDemoEnvironment.php index eb91dbb8b..3f82a38fc 100644 --- a/app/Jobs/ResetDemoEnvironment.php +++ b/app/Jobs/ResetDemoEnvironment.php @@ -9,7 +9,7 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Artisan; -use Illuminate\Support\Facades\DB; +use Laravel\Passport\ClientRepository; class ResetDemoEnvironment implements ShouldQueue { @@ -18,17 +18,18 @@ class ResetDemoEnvironment implements ShouldQueue use Queueable; use SerializesModels; - public function handle(EnvWriter $envWriter): void + public function handle(EnvWriter $envWriter, ClientRepository $clients): void { Artisan::call('migrate:refresh', ['--seed' => true, '--force' => true]); - Artisan::call('passport:client', [ - '--password' => true, - '--name' => 'Rose Vouchers Password Grant Client', - '--provider' => 'users', - ]); + $client = $clients->createPasswordGrantClient( + userId: null, + name: 'Rose Vouchers Password Grant Client', + redirect: '', + provider: 'users', + ); - $newSecret = DB::table('oauth_clients')->where('id', 1)->pluck('secret')[0]; - $envWriter->updateKey('PASSWORD_CLIENT_SECRET', $newSecret); + $envWriter->updateKey('PASSWORD_CLIENT_SECRET', $client->plainSecret); } } + diff --git a/tests/Unit/Jobs/ResetDemoEnvironmentTest.php b/tests/Unit/Jobs/ResetDemoEnvironmentTest.php index 59dc3d09e..4cc15139e 100644 --- a/tests/Unit/Jobs/ResetDemoEnvironmentTest.php +++ b/tests/Unit/Jobs/ResetDemoEnvironmentTest.php @@ -5,7 +5,8 @@ use App\Jobs\ResetDemoEnvironment; use App\Services\EnvWriter; use Illuminate\Support\Facades\Artisan; -use Illuminate\Support\Facades\DB; +use Laravel\Passport\Client; +use Laravel\Passport\ClientRepository; use Mockery; use Mockery\MockInterface; use Tests\TestCase; @@ -16,23 +17,29 @@ class ResetDemoEnvironmentTest extends TestCase // Helpers // ------------------------------------------------------------------------- - private function mockOauthClientQuery(string $secret = 'new-test-secret'): void + private function mockEnvWriter(): MockInterface { - $builder = Mockery::mock(); - $builder->shouldReceive('where')->with('id', 1)->andReturnSelf(); - $builder->shouldReceive('pluck')->with('secret')->andReturn(collect([$secret])); - - DB::shouldReceive('table')->with('oauth_clients')->andReturn($builder); + return $this->mock(EnvWriter::class); } - private function mockEnvWriter(): MockInterface + private function mockClientRepository(string $secret = 'new-test-secret'): MockInterface { - return $this->mock(EnvWriter::class); + $client = Mockery::mock(Client::class); + $client->plainSecret = $secret; + + $repository = $this->mock(ClientRepository::class); + $repository->shouldReceive('createPasswordGrantClient') + ->andReturn($client); + + return $repository; } private function dispatchJob(): void { - app(ResetDemoEnvironment::class)->handle(app(EnvWriter::class)); + app(ResetDemoEnvironment::class)->handle( + app(EnvWriter::class), + app(ClientRepository::class), + ); } // ------------------------------------------------------------------------- @@ -44,44 +51,28 @@ public function testRunsMigrateRefresh(): void Artisan::shouldReceive('call') ->once() ->with('migrate:refresh', ['--seed' => true, '--force' => true]); - Artisan::shouldReceive('call') - ->once() - ->with('passport:client', Mockery::any()); $this->mockEnvWriter()->shouldReceive('updateKey')->andReturnNull(); - $this->mockOauthClientQuery(); - - $this->dispatchJob(); - } - - public function testCreatesPassportClientWithCorrectArguments(): void - { - Artisan::shouldReceive('call')->once()->with('migrate:refresh', Mockery::any()); - Artisan::shouldReceive('call') - ->once() - ->with('passport:client', [ - '--password' => true, - '--name' => 'Rose Vouchers Password Grant Client', - '--provider' => 'users', - ]); - $this->mockEnvWriter()->shouldReceive('updateKey')->andReturnNull(); - $this->mockOauthClientQuery(); + $this->mockClientRepository(); $this->dispatchJob(); } // ------------------------------------------------------------------------- - // Secret retrieval + // Passport client creation // ------------------------------------------------------------------------- - public function testQueriesOauthClientsForNewSecret(): void + public function testCreatesPasswordGrantClientWithCorrectArguments(): void { Artisan::shouldReceive('call')->andReturn(0); $this->mockEnvWriter()->shouldReceive('updateKey')->andReturnNull(); - $builder = Mockery::mock(); - $builder->shouldReceive('where')->once()->with('id', 1)->andReturnSelf(); - $builder->shouldReceive('pluck')->once()->with('secret')->andReturn(collect(['expected-secret'])); - DB::shouldReceive('table')->once()->with('oauth_clients')->andReturn($builder); + $this->mock(ClientRepository::class) + ->shouldReceive('createPasswordGrantClient') + ->once() + ->with(null, 'Rose Vouchers Password Grant Client', '', 'users') + ->andReturn(tap(Mockery::mock(Client::class), function ($c) { + $c->plainSecret = 'test-secret'; + })); $this->dispatchJob(); } @@ -90,10 +81,10 @@ public function testQueriesOauthClientsForNewSecret(): void // EnvWriter // ------------------------------------------------------------------------- - public function testWritesNewSecretToEnvFile(): void + public function testWritesClientSecretToEnvFile(): void { Artisan::shouldReceive('call')->andReturn(0); - $this->mockOauthClientQuery('brand-new-secret'); + $this->mockClientRepository('brand-new-secret'); $this->mockEnvWriter() ->shouldReceive('updateKey') @@ -106,7 +97,7 @@ public function testWritesNewSecretToEnvFile(): void public function testWritesToCorrectEnvKey(): void { Artisan::shouldReceive('call')->andReturn(0); - $this->mockOauthClientQuery(); + $this->mockClientRepository(); $this->mockEnvWriter() ->shouldReceive('updateKey') From 5b333d24b617063f8c0d000ef6f5cded09e44aba Mon Sep 17 00:00:00 2001 From: charles strange Date: Fri, 3 Apr 2026 18:33:04 +0100 Subject: [PATCH 047/168] feature: make migration, add trait, alter model, update tests, facctory and seeds. --- app/CentreUser.php | 30 +- app/Traits/Retirable.php | 79 +++++ database/factories/ModelFactory.php | 9 + ..._163930_add_retired_at_to_centre_users.php | 27 ++ database/seeders/CentreUsersSeeder.php | 202 +++++++------ tests/Stubs/TestRetirableModel.php | 35 +++ tests/Unit/Models/CentreUserModelTest.php | 96 +++++- tests/Unit/Traits/RetirableTest.php | 281 ++++++++++++++++++ 8 files changed, 664 insertions(+), 95 deletions(-) create mode 100644 app/Traits/Retirable.php create mode 100644 database/migrations/2026_04_03_163930_add_retired_at_to_centre_users.php create mode 100644 tests/Stubs/TestRetirableModel.php create mode 100644 tests/Unit/Traits/RetirableTest.php diff --git a/app/CentreUser.php b/app/CentreUser.php index 7db1786cd..1759b1f19 100644 --- a/app/CentreUser.php +++ b/app/CentreUser.php @@ -2,15 +2,18 @@ namespace App; +use App\Notifications\StorePasswordResetNotification; +use App\Traits\Retirable; use Eloquent; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\belongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; -use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; +use Illuminate\Notifications\Notifiable; use Illuminate\Support\Collection; -use App\Notifications\StorePasswordResetNotification; +use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Str; /** * @mixin Eloquent @@ -27,6 +30,7 @@ class CentreUser extends Authenticatable { use Notifiable; use SoftDeletes; + use Retirable; protected string $guard = 'store'; @@ -36,7 +40,11 @@ class CentreUser extends Authenticatable * @var array */ protected $fillable = [ - 'name', 'email', 'password', 'role', 'downloader', + 'name', + 'email', + 'password', + 'role', + 'downloader', ]; /** @@ -45,7 +53,7 @@ class CentreUser extends Authenticatable * @var array */ protected $appends = [ - 'homeCentre' + 'homeCentre', ]; /** @@ -54,7 +62,8 @@ class CentreUser extends Authenticatable * @var array */ protected $hidden = [ - 'password', 'remember_token', + 'password', + 'remember_token', ]; /** @@ -67,6 +76,15 @@ class CentreUser extends Authenticatable 'deleted_at' => 'datetime', ]; + protected function retirableFields(): array + { + return [ + 'name' => '[User Retired]', + 'email' => 'retired_' . Str::uuid() . '@retired.invalid', + 'password' => Hash::make(Str::uuid()), + 'remember_token' => null, + ]; + } /** * Get the Notes that belong to this CentreUser */ @@ -152,7 +170,7 @@ public function isRelevantCentre(Centre $centre): bool /** * Send the password reset notification. * - * @param string $token + * @param string $token */ public function sendPasswordResetNotification($token): void { diff --git a/app/Traits/Retirable.php b/app/Traits/Retirable.php new file mode 100644 index 000000000..1733a623f --- /dev/null +++ b/app/Traits/Retirable.php @@ -0,0 +1,79 @@ +retired_at !== null) { + throw new DomainException( + "Retired " . get_class($model) . " [{$model->id}] cannot be restored." + ); + } + }); + } + + /** + * Magically invoked - Adds retired_at to the model + */ + public function initializeRetirable(): void + { + $this->casts['retired_at'] = 'datetime'; + } + + /** + * Map of field => retirement value. + * Override in the consuming class to match its DDL constraints. + */ + protected function retirableFields(): array + { + return [ + 'remember_token' => null, + ]; + } + + public function retire(): void + { + if ($this->retired_at !== null) { + return; + } + + $this->forceFill( + array_merge($this->retirableFields(), ['retired_at' => now()]) + )->save(); + + $this->delete(); + } + + public function isRetired(): bool + { + return $this->retired_at !== null; + } + + public function scopeRetired($query) + { + return $query->withTrashed()->whereNotNull('retired_at'); + } + + // Not trashed or retired + public function scopeActive($query) + { + return $query->whereNull('retired_at'); + } +} diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 630d8f701..899e243de 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -57,6 +57,15 @@ ]; }); +/** + * CentreUser in the retired state. + * PII is wiped, retired_at is set, and the model is soft-deleted. + * Relations (notes, centres) are preserved on the underlying row. + */ +$factory->afterCreatingState(App\CentreUser::class, 'retired', function ($centreUser) { + $centreUser->retire(); +}); + /** * CentreUser who can Download. */ diff --git a/database/migrations/2026_04_03_163930_add_retired_at_to_centre_users.php b/database/migrations/2026_04_03_163930_add_retired_at_to_centre_users.php new file mode 100644 index 000000000..1cb54e6c7 --- /dev/null +++ b/database/migrations/2026_04_03_163930_add_retired_at_to_centre_users.php @@ -0,0 +1,27 @@ +datetime('retired_at')->after('deleted_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('centre_users', static function (Blueprint $table) { + $table->dropColumn(['retired_at']); + }); + } +}; diff --git a/database/seeders/CentreUsersSeeder.php b/database/seeders/CentreUsersSeeder.php index aac58d0c4..866c52d57 100644 --- a/database/seeders/CentreUsersSeeder.php +++ b/database/seeders/CentreUsersSeeder.php @@ -1,117 +1,143 @@ create([ - "name" => "ARC CC User", - "email" => "arc+ccuser@neontribe.co.uk", - "password" => bcrypt('store_pass'), - "role" => "centre_user", - ]); + $this->defaultPassword = Hash::make('store_pass'); - // Attach an initial centre - $user1->centres()->attach([ - 1 => ['homeCentre' => true] - ]); + $this->seedCcUser(); + $this->seedNamedUsers(); + $this->seedRandomUsers(); + $this->seedDeletedUsers(); + $this->seedRetiredUsers(); + } - // Get the first centre's sponsor and make two more centres with the same sponsor - $sponsor_id = $user1->centres()->first()->sponsor->id; + /** + * CC user requires additional centre setup so is handled separately. + */ + private function seedCcUser(): void + { + $user = $this->createAndAttach( + attributes: ['name' => 'ARC CC User', 'email' => 'arc+ccuser@exmaple.com', 'role' => 'centre_user'], + centreId: 1, + isHome: true + ); - $local_centres = factory(Centre::class, 2)->create(['sponsor_id' => $sponsor_id]); + // Two extra centres sharing the same sponsor, attached as non-home. + $sponsorId = $user->centres()->first()->sponsor->id; + $localCentres = factory(Centre::class, 2)->create(['sponsor_id' => $sponsorId]); - // Attach the extra centres - $user1->centres()->attach([ - $local_centres[0]->id => ['homeCentre' => false], - $local_centres[1]->id => ['homeCentre' => false], + $user->centres()->attach([ + $localCentres[0]->id => ['homeCentre' => false], + $localCentres[1]->id => ['homeCentre' => false], ]); + } - $user2 = factory(CentreUser::class)->state('FMUser')->create([ - "name" => "ARC FM User", - "email" => "arc+fmuser@neontribe.co.uk", - "password" => bcrypt('store_pass'), - ]); - $user2->centres()->attach(1, ['homeCentre' => true]); + /** + * Named users with fixed centre assignments. + * Each entry: [ state, name, email, centreId | centreName ] + */ + private function seedNamedUsers(): void + { + $users = [ + ['FMUser', 'ARC FM User', 'arc+fmuser@exmaple.com', 1], + ['FMUser', 'ARC fmuser2', 'arc+fmuser2@exmaple.com', 2], + ['', 'prescribing user', 'arc+spuser@exmaple.com', 'Prescribing Centre'], + ['', 'Scottish user', 'arc+scuser@exmaple.com', 8], + ['', 'Southwark user', 'arc+swuser@exmaple.com', 6], + ['', 'Tower Hamlet SP user', 'arc+thuser@exmaple.com', 10], + ['', 'Lambeth SP user', 'arc+lambethuser@exmaple.com', 11], + ]; - // ARC admin is an fmuser in centre 2, which has individual forms on the dashboard. - $user3 = factory(CentreUser::class)->state('FMUser')->create([ - "name" => "ARC fmuser2", - "email" => "arc+fmuser2@neontribe.co.uk", - "password" => bcrypt('store_pass'), - ]); - $user3->centres()->attach(2, ['homeCentre' => true]); + foreach ($users as [$state, $name, $email, $centre]) { + $centreId = is_int($centre) + ? $centre + : Centre::where('name', $centre)->first()->id; - // 4 faked users associated with random Centres + $this->createAndAttach( + attributes: ['name' => $name, 'email' => $email], + centreId: $centreId, + state: $state + ); + } + } + + /** + * Four faked users each attached to a random existing centre. + */ + private function seedRandomUsers(): void + { factory(CentreUser::class, 4) ->create() - ->each(function ($centreUser) { - $centres = Centre::get(); - if ($centres->count() > 0) { - // Pick a random Centre - $centre = $centres[random_int(0, $centres->count()-1)]; - } else { - // There should be at least one Centre - $centre = factory(Centre::class)->create(); - } + ->each(function (CentreUser $centreUser) { + $centre = Centre::inRandomOrder()->first() + ?? factory(Centre::class)->create(); + $centreUser->centres()->attach($centre->id, ['homeCentre' => true]); }); + } - // 2 deleted users - $deletedUsers = factory(CentreUser::class, 2)->create([ - "deleted_at" => date("Y-m-d H:i:s"), - ]); + /** + * Two soft-deleted users. + */ + private function seedDeletedUsers(): void + { + factory(CentreUser::class, 2)->create(['deleted_at' => now()]); + } - // an SP user for testing - $socialPrescriber = factory(CentreUser::class)->create([ - 'name' => 'prescribing user', - 'email' => 'arc+spuser@neontribe.co.uk', - "password" => bcrypt('store_pass'), - ]); - $spcId = Centre::where('name', 'Prescribing Centre')->first()->id; - $socialPrescriber->centres()->attach($spcId, ["homeCentre" => true]); - - // Scottish user for testing - $scottishUser = factory(CentreUser::class)->create([ - 'name' => 'Scottish user', - 'email' => 'arc+scuser@neontribe.co.uk', - 'password' => bcrypt('store_pass'), - ]); - $scottishUser->centres()->attach(8, ['homeCentre' => true]); + /** + * Two retired users, each attached to a random centre before retirement. + * + * Centre relations are preserved on the underlying row after retirement. + * Retirement is called directly rather than via the factory state because + * the centre must be attached first — retire() must run last. + */ + private function seedRetiredUsers(): void + { + factory(CentreUser::class, 2) + ->create() + ->each(function (CentreUser $centreUser) { + $centre = Centre::inRandomOrder()->first() + ?? factory(Centre::class)->create(); - // Southwark user for testing - $southwarkUser = factory(CentreUser::class)->create([ - 'name' => 'Southwark user', - 'email' => 'arc+swuser@neontribe.co.uk', - 'password' => bcrypt('store_pass'), - ]); - $southwarkUser->centres()->attach(6, ['homeCentre' => true]); - - // Tower Hamlet SP user for testing - $towerHamletUser = factory(CentreUser::class)->create([ - 'name' => 'Tower Hamlet SP user', - 'email' => 'arc+thuser@neontribe.co.uk', - 'password' => bcrypt('store_pass'), - ]); - $towerHamletUser->centres()->attach(10, ['homeCentre' => true]); - - // Lambeth SP user for testing - $lambethUser = factory(CentreUser::class)->create([ - 'name' => 'Lambeth SP user', - 'email' => 'arc+lambethuser@neontribe.co.uk', - 'password' => bcrypt('store_pass'), - ]); - $lambethUser->centres()->attach(11, ['homeCentre' => true]); + $centreUser->centres()->attach($centre->id, ['homeCentre' => true]); + $centreUser->retire(); + }); + } + + /** + * Create a CentreUser and attach them to a centre in one step. + */ + private function createAndAttach( + array $attributes, + int $centreId, + bool $isHome = true, + string $state = '' + ): CentreUser { + $attributes['password'] ??= $this->defaultPassword; + + $builder = factory(CentreUser::class); + + if ($state !== '') { + $builder = $builder->state($state); + } + + $user = $builder->create($attributes); + $user->centres()->attach($centreId, ['homeCentre' => $isHome]); + + return $user; } } diff --git a/tests/Stubs/TestRetirableModel.php b/tests/Stubs/TestRetirableModel.php new file mode 100644 index 000000000..2734eff91 --- /dev/null +++ b/tests/Stubs/TestRetirableModel.php @@ -0,0 +1,35 @@ + 'retired', + 'email' => 'retired_' . Str::uuid() . '@retired.invalid', + 'password' => Hash::make(Str::uuid()), + 'remember_token' => null, + ]; + } +} diff --git a/tests/Unit/Models/CentreUserModelTest.php b/tests/Unit/Models/CentreUserModelTest.php index 8d316670a..a208b4812 100644 --- a/tests/Unit/Models/CentreUserModelTest.php +++ b/tests/Unit/Models/CentreUserModelTest.php @@ -5,8 +5,10 @@ use App\Centre; use App\CentreUser; use App\Note; -use Tests\TestCase; +use DomainException; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\Hash; +use Tests\TestCase; class CentreUserModelTest extends TestCase { @@ -14,6 +16,7 @@ class CentreUserModelTest extends TestCase protected $centreUser; protected $notes; + protected function setUp(): void { parent::setUp(); @@ -89,4 +92,95 @@ public function testCentreUserCanHaveAlternativeCentres(): void // But We have no homeCentre $this->assertEmpty($cu->homeCentre); } + + // ----------------------------------------------------------------------- + // Retirable — CentreUser-specific tests + // + // These tests cover the concrete PII replacement values declared in + // CentreUser::retirableFields() and the survival of CentreUser's own + // relations after retirement. Generic trait behaviour (idempotency, + // soft-delete, scopes, boot guard) is covered in RetirableTest. + // ----------------------------------------------------------------------- + + public function testCentreUserIsNotRetiredByDefault(): void + { + $this->assertFalse($this->centreUser->isRetired()); + $this->assertNull($this->centreUser->retired_at); + } + + public function testRetiredCentreUserHasNameCleared(): void + { + $this->centreUser->retire(); + + $fresh = CentreUser::withTrashed()->find($this->centreUser->id); + $this->assertSame('[User Retired]', $fresh->name); + } + + public function testRetiredCentreUserHasEmailReplacedWithSafeRetiredPlaceholder(): void + { + $originalEmail = $this->centreUser->email; // capture before retire() mutates the instance + + $this->centreUser->retire(); + + $email = CentreUser::withTrashed()->find($this->centreUser->id)->email; + + // Confirm format matches CentreUser::retirableFields() convention. + $this->assertStringStartsWith('retired_', $email); + $this->assertStringEndsWith('@retired.invalid', $email); + + // Confirm the original email is gone. + $this->assertNotSame($originalEmail, $email); + } + + public function testRetiredCentreUserHasPasswordReplacedWithANewHash(): void + { + $originalPassword = $this->centreUser->password; // capture before retire() mutates the instance + + $this->centreUser->retire(); + + $replacedPassword = CentreUser::withTrashed()->find($this->centreUser->id)->password; + + $this->assertTrue(Hash::isHashed($replacedPassword)); + $this->assertNotSame($originalPassword, $replacedPassword); + } + + public function testRetiredCentreUserHasRememberTokenCleared(): void + { + $this->centreUser->retire(); + + $fresh = CentreUser::withTrashed()->find($this->centreUser->id); + $this->assertNull($fresh->remember_token); + } + + public function testRetiredCentreUserRetainsNoteRelations(): void + { + // Notes exist before retirement. + $this->assertCount(2, $this->centreUser->notes); + + $this->centreUser->retire(); + + // Notes are still associated via FK after retirement. + $fresh = CentreUser::withTrashed()->find($this->centreUser->id); + $this->assertCount(2, $fresh->notes); + } + + public function testRetiredCentreUserRetainsCentreRelations(): void + { + $centre = factory(Centre::class)->create(); + $this->centreUser->centres()->attach($centre->id, ['homeCentre' => true]); + + $this->centreUser->retire(); + + $fresh = CentreUser::withTrashed()->find($this->centreUser->id); + $this->assertEquals(1, $fresh->centres()->count()); + } + + public function testRetiredCentreUserCannotBeRestored(): void + { + $cu = factory(CentreUser::class)->state('retired')->create()->fresh(); + + $this->expectException(DomainException::class); + + CentreUser::withTrashed()->find($cu->id)->restore(); + } } diff --git a/tests/Unit/Traits/RetirableTest.php b/tests/Unit/Traits/RetirableTest.php new file mode 100644 index 000000000..dbb755149 --- /dev/null +++ b/tests/Unit/Traits/RetirableTest.php @@ -0,0 +1,281 @@ +id(); + $table->string('name'); + $table->string('email')->unique(); + $table->string('password'); + $table->string('remember_token')->nullable(); + $table->timestamps(); + $table->softDeletes(); + $table->timestamp('retired_at')->nullable(); + }); + } + + protected function tearDown(): void + { + Schema::dropIfExists('retirable_stubs'); + + // Clear Eloquent's static boot cache so each test begins with a clean + // boot cycle — essential for the boot guard test to be repeatable. + Model::clearBootedModels(); + + parent::tearDown(); + } + + // ----------------------------------------------------------------------- + // Helpers + // ----------------------------------------------------------------------- + + private function makeStub(array $attributes = []): TestRetirableModel + { + return TestRetirableModel::create(array_merge([ + 'name' => 'Original Name', + 'email' => 'original@example.com', + 'password' => Hash::make('secret'), + ], $attributes)); + } + + // ----------------------------------------------------------------------- + // Boot guard + // ----------------------------------------------------------------------- + + + public function testBootRetirableThrowsLogicExceptionIfSoftDeletesIsNotUsed(): void + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('must use SoftDeletes'); + + new RetirableWithoutSoftDeletesStub(); + } + + // ----------------------------------------------------------------------- + // Cast registration + // ----------------------------------------------------------------------- + + + public function testRetiredAtIsAutomaticallyCastToDatetime(): void + { + $stub = $this->makeStub(); + $stub->retired_at = now(); + + $this->assertInstanceOf(Carbon::class, $stub->retired_at); + } + + // ----------------------------------------------------------------------- + // retire() + // ----------------------------------------------------------------------- + + + public function testRetireSetsRetiredAtToCurrentTime(): void + { + $stub = $this->makeStub(); + + $stub->retire(); + + $this->assertNotNull($stub->fresh()->retired_at); + } + + + public function testRetireSoftDeletesTheModel(): void + { + $stub = $this->makeStub(); + + $stub->retire(); + + $this->assertSoftDeleted('retirable_stubs', ['id' => $stub->id]); + } + + + public function testRetireReplacesEachFieldDeclaredInRetirableFields(): void + { + $stub = $this->makeStub(); + $originalEmail = $stub->email; + $originalPassword = $stub->password; + + $stub->retire(); + + $fresh = TestRetirableModel::withTrashed()->find($stub->id); + + $this->assertNotSame($originalEmail, $fresh->email); + $this->assertNotSame($originalPassword, $fresh->password); + $this->assertNull($fresh->remember_token); + } + + + public function testRetireStoresAValidEmailPlaceholder(): void + { + $stub = $this->makeStub(); + + $stub->retire(); + + $email = TestRetirableModel::withTrashed()->find($stub->id)->email; + + $this->assertStringStartsWith('retired_', $email); + $this->assertStringEndsWith('@retired.invalid', $email); + } + + + public function testRetireStoresAHashedPasswordNotPlainText(): void + { + $stub = $this->makeStub(); + + $stub->retire(); + + $password = TestRetirableModel::withTrashed()->find($stub->id)->password; + + $this->assertTrue(Hash::isHashed($password)); + } + + + public function testRetireIsIdempotentAndDoesNotChangeFieldsOnSecondCall(): void + { + $stub = $this->makeStub(); + $stub->retire(); + + $afterFirst = TestRetirableModel::withTrashed()->find($stub->id); + $emailAfterFirst = $afterFirst->email; + $retiredAtAfterFirst = $afterFirst->retired_at->toDateTimeString(); + + $stub->retire(); + + $afterSecond = TestRetirableModel::withTrashed()->find($stub->id); + + $this->assertSame($emailAfterFirst, $afterSecond->email); + $this->assertSame($retiredAtAfterFirst, $afterSecond->retired_at->toDateTimeString()); + } + + + public function testTwoSeparatelyRetiredModelsReceiveUniqueEmailPlaceholders(): void + { + $first = $this->makeStub(['email' => 'first@example.com']); + $second = $this->makeStub(['email' => 'second@example.com']); + + $first->retire(); + $second->retire(); + + $emailFirst = TestRetirableModel::withTrashed()->find($first->id)->email; + $emailSecond = TestRetirableModel::withTrashed()->find($second->id)->email; + + $this->assertNotSame($emailFirst, $emailSecond); + } + + // ----------------------------------------------------------------------- + // isRetired() + // ----------------------------------------------------------------------- + + + public function testIsRetiredReturnsFalseForAnActiveModel(): void + { + $stub = $this->makeStub(); + + $this->assertFalse($stub->isRetired()); + } + + + public function testIsRetiredReturnsTrueAfterRetirement(): void + { + $stub = $this->makeStub(); + + $stub->retire(); + + $this->assertTrue($stub->isRetired()); + } + + // ----------------------------------------------------------------------- + // Restore guard (restoring event) + // ----------------------------------------------------------------------- + + + public function testRestoringARetiredModelThrowsADomainException(): void + { + $stub = $this->makeStub(); + $stub->retire(); + + $this->expectException(DomainException::class); + $this->expectExceptionMessage('cannot be restored'); + + TestRetirableModel::withTrashed()->find($stub->id)->restore(); + } + + + public function testRestoringAMerelySoftDeletedModelSucceeds(): void + { + $stub = $this->makeStub(); + $stub->delete(); + + TestRetirableModel::withTrashed()->find($stub->id)->restore(); + + $this->assertNotSoftDeleted('retirable_stubs', ['id' => $stub->id]); + } + + // ----------------------------------------------------------------------- + // Scopes + // ----------------------------------------------------------------------- + + + public function testScopeRetiredReturnsOnlyRetiredModels(): void + { + $active = $this->makeStub(['email' => 'active@example.com']); + $deleted = $this->makeStub(['email' => 'deleted@example.com']); + $retired = $this->makeStub(['email' => 'retired@example.com']); + + $deleted->delete(); + $retired->retire(); + + $results = TestRetirableModel::retired()->get(); + + $this->assertCount(1, $results); + $this->assertTrue($results->first()->is($retired)); + } + + + public function testScopeActiveExcludesRetiredAndSoftDeletedModels(): void + { + $active = $this->makeStub(['email' => 'active@example.com']); + $deleted = $this->makeStub(['email' => 'deleted@example.com']); + $retired = $this->makeStub(['email' => 'retired@example.com']); + + $deleted->delete(); + $retired->retire(); + + $results = TestRetirableModel::active()->get(); + + $this->assertCount(1, $results); + $this->assertTrue($results->first()->is($active)); + } +} From 99c99c0575f13fc7bcc344e4202b31141471723d Mon Sep 17 00:00:00 2001 From: charles strange Date: Fri, 3 Apr 2026 18:41:12 +0100 Subject: [PATCH 048/168] fix: email addresses in seeds --- database/seeders/CentreUsersSeeder.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/database/seeders/CentreUsersSeeder.php b/database/seeders/CentreUsersSeeder.php index 866c52d57..416eab6f4 100644 --- a/database/seeders/CentreUsersSeeder.php +++ b/database/seeders/CentreUsersSeeder.php @@ -53,13 +53,13 @@ private function seedCcUser(): void private function seedNamedUsers(): void { $users = [ - ['FMUser', 'ARC FM User', 'arc+fmuser@exmaple.com', 1], - ['FMUser', 'ARC fmuser2', 'arc+fmuser2@exmaple.com', 2], - ['', 'prescribing user', 'arc+spuser@exmaple.com', 'Prescribing Centre'], - ['', 'Scottish user', 'arc+scuser@exmaple.com', 8], - ['', 'Southwark user', 'arc+swuser@exmaple.com', 6], - ['', 'Tower Hamlet SP user', 'arc+thuser@exmaple.com', 10], - ['', 'Lambeth SP user', 'arc+lambethuser@exmaple.com', 11], + ['FMUser', 'ARC FM User', 'arc+fmuser@neontribe.co.uk', 1], + ['FMUser', 'ARC fmuser2', 'arc+fmuser2@neontribe.co.uk', 2], + ['', 'prescribing user', 'arc+spuser@neontribe.co.uk', 'Prescribing Centre'], + ['', 'Scottish user', 'arc+scuser@neontribe.co.uk', 8], + ['', 'Southwark user', 'arc+swuser@neontribe.co.uk', 6], + ['', 'Tower Hamlet SP user', 'arc+thuser@neontribe.co.uk', 10], + ['', 'Lambeth SP user', 'arc+lambethuser@neontribe.co.uk', 11], ]; foreach ($users as [$state, $name, $email, $centre]) { From 40bc3eb8b3de5f6301ac303eb4231b2d7177f144 Mon Sep 17 00:00:00 2001 From: charles strange Date: Sun, 5 Apr 2026 16:52:36 +0100 Subject: [PATCH 049/168] feature: update controller function, edit page text and routes, add tests --- .../Service/Admin/CentreUsersController.php | 40 ++--- app/Traits/Retirable.php | 1 + .../views/service/centreusers/edit.blade.php | 14 +- routes/service.php | 6 +- tests/Feature/Service/EditWorkerPageTest.php | 169 ++++++++++++++++-- .../Admin/CentreUserControllerTest.php | 123 +++++++++---- 6 files changed, 271 insertions(+), 82 deletions(-) diff --git a/app/Http/Controllers/Service/Admin/CentreUsersController.php b/app/Http/Controllers/Service/Admin/CentreUsersController.php index 69175fcd8..b95f8950a 100644 --- a/app/Http/Controllers/Service/Admin/CentreUsersController.php +++ b/app/Http/Controllers/Service/Admin/CentreUsersController.php @@ -23,16 +23,13 @@ use Log; use Ramsey\Uuid\Uuid; use Throwable; -use function PHPUnit\Framework\isNan; class CentreUsersController extends Controller { /** * Display a listing of Workers. - * @param AdminIndexCentreUsersRequest $request - * @return Application|Factory|View */ - public function index(AdminIndexCentreUsersRequest $request): View|Factory|Application + public function index(AdminIndexCentreUsersRequest $request): Factory|View { // fetch query params from request $field = $request->input('orderBy'); @@ -47,7 +44,7 @@ public function index(AdminIndexCentreUsersRequest $request): View|Factory|Appli return $homeCentre?->sponsor?->name . '#' . $homeCentre?->name . '#' . $item->name; }, }; - $workers = CentreUser::withTrashed()->get()->sortBy($sorter, SORT_REGULAR, ($direction === 'desc')); + $workers = CentreUser::withTrashed()->active()->get()->sortBy($sorter, SORT_REGULAR, ($direction === 'desc')); return view('service.centreusers.index', compact('workers')); } @@ -101,12 +98,9 @@ public function edit($id): View|Factory|Application /** * Update a CentreUser from a form - * @param AdminUpdateCentreUserRequest $request - * @param $id - * @return RedirectResponse * @throws Throwable */ - public function update(AdminUpdateCentreUserRequest $request, $id) + public function update(AdminUpdateCentreUserRequest $request, $id): RedirectResponse { try { $centreUser = DB::transaction(function () use ($request, $id) { @@ -140,11 +134,8 @@ public function update(AdminUpdateCentreUserRequest $request, $id) /** * Code deduplication; - * @param Request $request - * @param CentreUser $cu - * @return array */ - private function syncCentres(Request $request, CentreUser $cu): array + private function syncCentres(Request $request, CentreUser $cu): void { // Set Home Centre $homeCentre_id = $request->input('worker_centre'); @@ -162,13 +153,11 @@ private function syncCentres(Request $request, CentreUser $cu): array } } // Sync them setting pivots. - return $cu->centres()->sync($centre_ids); + $cu->centres()->sync($centre_ids); } /** * Create a CentreUser from a form - * @param AdminNewCentreUserRequest $request - * @return RedirectResponse * @throws Throwable */ public function store(AdminNewCentreUserRequest $request): RedirectResponse @@ -196,12 +185,13 @@ public function store(AdminNewCentreUserRequest $request): RedirectResponse // Throw it back to the user return redirect()->route('admin.centreusers.create')->withErrors('Creation failed - DB Error.'); } - return redirect()->route('admin.centreusers.index')->with('message', - 'Worker ' . $centreUser->name . ' created'); + return redirect()->route('admin.centreusers.index')->with( + 'message', + 'Worker ' . $centreUser->name . ' created' + ); } /** - * @return void * @throws CannotInsertRecord */ public function download(): void @@ -253,20 +243,18 @@ public function download(): void /** * Handle deleting a centre user - * @param int $id - * @return RedirectResponse */ - public function delete(int $id): RedirectResponse + public function retire(int $id): RedirectResponse { // must be disabled to delete $centreUser = CentreUser::onlyTrashed()->findOrFail($id); $name = $centreUser->name; - // remove any connections - $centreUser->centre->centreUsers()->detach($id); + // boot them - $centreUser->forceDelete(); + $centreUser->retire(); + return redirect()->route('admin.centreusers.index') - ->with('message', 'Worker ' . $name . ' deleted'); + ->with('message', 'Worker ' . $name . ' retired'); } /** diff --git a/app/Traits/Retirable.php b/app/Traits/Retirable.php index 1733a623f..230b7603a 100644 --- a/app/Traits/Retirable.php +++ b/app/Traits/Retirable.php @@ -58,6 +58,7 @@ public function retire(): void array_merge($this->retirableFields(), ['retired_at' => now()]) )->save(); + // if we're not already soft deleted, do that too. $this->delete(); } diff --git a/resources/views/service/centreusers/edit.blade.php b/resources/views/service/centreusers/edit.blade.php index d5b4583ce..7245775c0 100644 --- a/resources/views/service/centreusers/edit.blade.php +++ b/resources/views/service/centreusers/edit.blade.php @@ -80,10 +80,10 @@ class="{{ $errors->has('worker_centre') ? 'error' : '' }}" required> @if($worker->deleted_at) -
+ {!! csrf_field() !!} - +
@endif diff --git a/routes/service.php b/routes/service.php index dbc9dbae5..c9cae592b 100644 --- a/routes/service.php +++ b/routes/service.php @@ -121,9 +121,9 @@ 'uses' => 'Admin\CentreUsersController@toggle', ])->where('id', '^[0-9]+$'); - Route::get('workers/{id}/delete', [ - 'as' => 'admin.centreusers.delete', - 'uses' => 'Admin\CentreUsersController@delete', + Route::get('workers/{id}/retire', [ + 'as' => 'admin.centreusers.retire', + 'uses' => 'Admin\CentreUsersController@retire', ])->where('id', '^[0-9]+$'); // Centre Management diff --git a/tests/Feature/Service/EditWorkerPageTest.php b/tests/Feature/Service/EditWorkerPageTest.php index 6db06ff67..d29e4bc20 100644 --- a/tests/Feature/Service/EditWorkerPageTest.php +++ b/tests/Feature/Service/EditWorkerPageTest.php @@ -13,14 +13,13 @@ class EditWorkerPageTest extends StoreTestCase { use RefreshDatabase; - /** @var AdminUser $adminUser */ - private $adminUser; + private AdminUser $adminUser; - /** @var Centre $centre */ - private $centre; + private Centre $centre; - /** @var Collection $altCentres */ - private $altCentres; + private Collection $altCentres; + + private CentreUser $worker; public function setUp(): void { @@ -28,20 +27,21 @@ public function setUp(): void $this->adminUser = factory(AdminUser::class)->create(); $this->centre = factory(Centre::class)->create([]); $this->altCentres = factory(Centre::class, 2)->create([]); + + $this->worker = factory(CentreUser::class)->create([ + 'name' => 'testman', + 'email' => 'testman@test.co.uk', + ]); + $this->worker->centres()->attach($this->centre->id, ['homeCentre' => true]); } - /** - * @return void - */ + // ----------------------------------------------------------------------- + // Page structure — active worker + // ----------------------------------------------------------------------- + public function testItShowsAWorkerEditPage(): void { - // Make a CentreUser from the data with 1 homeCentre. - $cu = factory(CentreUser::class)->create([ - 'name' => 'testman', - 'email' => 'testman@test.co.uk', - ]); - $cu->centres()->attach($this->altCentres->last()->id, ['homeCentre' => true]); - $workerEditRoute = route('admin.centreusers.edit', ['id' => $cu->id,]); + $workerEditRoute = route('admin.centreusers.edit', ['id' => $this->worker->id]); $this->actingAs($this->adminUser, 'admin') ->get($workerEditRoute) @@ -59,4 +59,141 @@ public function testItShowsAWorkerEditPage(): void ->seeElement('select[name="downloader"]') ; } + + public function testActiveWorkerShowsUpdateButton(): void + { + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.edit', ['id' => $this->worker->id])) + ->assertResponseOk() + ->seeElement('#updateWorker') + ; + } + + public function testActiveWorkerShowsDisableButtonNotEnableButton(): void + { + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.edit', ['id' => $this->worker->id])) + ->assertResponseOk() + ->seeInElement('#toggleWorker', 'Disable worker') + ->dontSeeInElement('#toggleWorker', 'Enable worker') + ; + } + + public function testActiveWorkerDoesNotShowRetireForm(): void + { + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.edit', ['id' => $this->worker->id])) + ->assertResponseOk() + ->dontSeeElement('#retireForm') + ->dontSeeElement('#retireWorker') + ; + } + + public function testActiveWorkerDoesNotShowDisabledMessage(): void + { + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.edit', ['id' => $this->worker->id])) + ->assertResponseOk() + ->dontSee('This worker is') + ; + } + + public function testWorkerHomeCentreIsPreselectedInDropdown(): void + { + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.edit', ['id' => $this->worker->id])) + ->assertResponseOk() + ->seeElement('option[value="' . $this->centre->id . '"][selected]') + ; + } + + public function testWorkerAlternativeCentresAreRenderedAsOptions(): void + { + $this->worker->centres()->attach([ + $this->altCentres[0]->id => ['homeCentre' => false], + $this->altCentres[1]->id => ['homeCentre' => false], + ]); + + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.edit', ['id' => $this->worker->id])) + ->assertResponseOk() + ->seeElement('option[value="' . $this->altCentres[0]->id . '"]') + ->seeElement('option[value="' . $this->altCentres[1]->id . '"]') + ; + } + + // ----------------------------------------------------------------------- + // Page structure — disabled (soft-deleted) worker + // ----------------------------------------------------------------------- + + public function testDisabledWorkerShowsDisabledMessage(): void + { + $this->worker->delete(); + + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.edit', ['id' => $this->worker->id])) + ->assertResponseOk() + ->seeInElement('h2', 'This worker is') + ->seeInElement('h2 i', 'disabled') + ; + } + + public function testDisabledWorkerDoesNotShowUpdateButton(): void + { + $this->worker->delete(); + + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.edit', ['id' => $this->worker->id])) + ->assertResponseOk() + ->dontSeeElement('#updateWorker') + ; + } + + public function testDisabledWorkerShowsEnableButtonNotDisableButton(): void + { + $this->worker->delete(); + + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.edit', ['id' => $this->worker->id])) + ->assertResponseOk() + ->seeInElement('#toggleWorker', 'Enable worker') + ->dontSeeInElement('#toggleWorker', 'Disable worker') + ; + } + + public function testDisabledWorkerShowsRetireForm(): void + { + $this->worker->delete(); + + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.edit', ['id' => $this->worker->id])) + ->assertResponseOk() + ->seeElement('#retireForm') + ->seeElement('#retireWorker') + ; + } + + public function testDisabledWorkerRetireFormPostsToCorrectRoute(): void + { + $this->worker->delete(); + + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.edit', ['id' => $this->worker->id])) + ->assertResponseOk() + ->seeElement( + '#retireForm[action="' . route('admin.centreusers.retire', ['id' => $this->worker->id]) . '"]' + ) + ; + } + + // ----------------------------------------------------------------------- + // Access control + // ----------------------------------------------------------------------- + + public function testUnauthenticatedUserCannotAccessEditPage(): void + { + $this->get(route('admin.centreusers.edit', ['id' => $this->worker->id])) + ->assertResponseStatus(302) + ; + } } diff --git a/tests/Unit/Controllers/Service/Admin/CentreUserControllerTest.php b/tests/Unit/Controllers/Service/Admin/CentreUserControllerTest.php index 257592b71..b2d605d95 100644 --- a/tests/Unit/Controllers/Service/Admin/CentreUserControllerTest.php +++ b/tests/Unit/Controllers/Service/Admin/CentreUserControllerTest.php @@ -46,8 +46,7 @@ public function testICanStoreACentreUser(): void ->assertResponseOk() ->seePageIs(route('admin.centreusers.index')) ->see($this->data["name"]) - ->see($this->data["email"]) - ; + ->see($this->data["email"]); // find the user $cu = CentreUser::where('email', $this->data['email'])->first(); $this->assertNotNull($cu); @@ -70,7 +69,7 @@ public function testItCanUpdateACentreUser(): void $this->seeInDatabase('centre_users', [ 'name' => $cu->name, 'email' => $cu->email, - 'downloader' => $cu->downloader + 'downloader' => $cu->downloader, ]); // Check that worked. $this->assertCount(1, $cu->centres); @@ -94,14 +93,12 @@ public function testItCanUpdateACentreUser(): void ->seeInDatabase('centre_users', [ 'name' => $this->data['name'], 'email' => $this->data['email'], - 'downloader' => $this->data['downloader'] + 'downloader' => $this->data['downloader'], ]) ->dontSeeInDatabase('centre_users', [ 'name' => $cu->name, 'email' => $cu->email, - ]) - ; - ; + ]); // find the user $cu = CentreUser::where('email', $this->data['email'])->first(); $this->assertNotNull($cu); @@ -131,18 +128,16 @@ public function testItCanDisableACentreUser(): void ) ->followRedirects() ->assertResponseOk() - // returns to edit page ->seePageIs(route('admin.centreusers.edit', ['id' => $cu->id])) - // retains old information + // Centre pivot relations are preserved on disable. ->seeInDatabase('centre_centre_user', [ 'centre_user_id' => $cu->id, 'centre_id' => $this->altCentres->last()->id, - ]) - ->see('This worker is disabled') - ->seeInElement('#toggleWorker', ' Enable worker') - // There is a delete button - ->seeElement('#deleteWorker') - ; + ]); + + // Record is soft-deleted but still present. + $this->assertNull(CentreUser::find($cu->id)); + $this->assertNotNull(CentreUser::withTrashed()->find($cu->id)->deleted_at); } public function testItCanEnableACentreUser(): void @@ -150,7 +145,7 @@ public function testItCanEnableACentreUser(): void $cu = factory(CentreUser::class)->create([ 'name' => "testman", 'email' => "testman@test.co.uk", - 'deleted_at' => date("Y-m-d H:i:s") + 'deleted_at' => date("Y-m-d H:i:s"), ]); $cu->centres()->attach($this->altCentres->last()->id, ['homeCentre' => true]); @@ -166,16 +161,16 @@ public function testItCanEnableACentreUser(): void ) ->followRedirects() ->assertResponseOk() - // returns to edit page ->seePageIs(route('admin.centreusers.edit', ['id' => $cu->id])) - // retains old information + // Centre pivot relations are preserved on enable. ->seeInDatabase('centre_centre_user', [ 'centre_user_id' => $cu->id, 'centre_id' => $this->altCentres->last()->id, - ]) - ->dontSee('This worker is disabled') - ->seeInElement('#toggleWorker', 'Disable worker') - ; + ]); + + // Record is restored and findable without withTrashed(). + $this->assertNotNull(CentreUser::find($cu->id)); + $this->assertNull(CentreUser::find($cu->id)->deleted_at); } public function testICanSeeDisabledCentreUsers(): void @@ -183,20 +178,19 @@ public function testICanSeeDisabledCentreUsers(): void $cu = factory(CentreUser::class)->create([ 'name' => "testman", 'email' => "testman@test.co.uk", - 'deleted_at' => date("Y-m-d H:i:s") + 'deleted_at' => date("Y-m-d H:i:s"), ]); $this->seeInDatabase('centre_users', [ 'name' => $cu->name, 'email' => $cu->email, - 'deleted_at' => date("Y-m-d H:i:s") + 'deleted_at' => date("Y-m-d H:i:s"), ]); $this->actingAs($this->adminUser, 'admin') ->visit(route('admin.centreusers.index')) ->assertResponseOk() - ->see($cu->email) - ; + ->see($cu->email); } public function testICannotSeeDeletedCentreUsers(): void @@ -204,13 +198,13 @@ public function testICannotSeeDeletedCentreUsers(): void $cu = factory(CentreUser::class)->create([ 'name' => "testman", 'email' => "testman@test.co.uk", - 'deleted_at' => date("Y-m-d H:i:s") + 'deleted_at' => date("Y-m-d H:i:s"), ]); $this->seeInDatabase('centre_users', [ 'name' => $cu->name, 'email' => $cu->email, - 'deleted_at' => date("Y-m-d H:i:s") + 'deleted_at' => date("Y-m-d H:i:s"), ]); // remove it! @@ -219,7 +213,76 @@ public function testICannotSeeDeletedCentreUsers(): void $this->actingAs($this->adminUser, 'admin') ->visit(route('admin.centreusers.index')) ->assertResponseOk() - ->dontSee($cu->email) - ; + ->dontSee($cu->email); + } + + // ----------------------------------------------------------------------- + // Retire action + // ----------------------------------------------------------------------- + + public function testRetiringADisabledWorkerWipesTheirPiiAndRedirectsToIndex(): void + { + $cu = factory(CentreUser::class)->create(); + $cu->centres()->attach($this->centre->id, ['homeCentre' => true]); + $cu->delete(); + + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.retire', ['id' => $cu->id])) + ->assertRedirectedToRoute('admin.centreusers.index') + ->assertSessionHas('message'); + + $retired = CentreUser::withTrashed()->find($cu->id); + $this->assertNotNull($retired->retired_at); + $this->assertSame('[User Retired]', $retired->name); + $this->assertStringStartsWith('retired_', $retired->email); + $this->assertStringEndsWith('@retired.invalid', $retired->email); + } + + public function testRetiringADisabledWorkerRetainsTheirCentreRelations(): void + { + $cu = factory(CentreUser::class)->create(); + $cu->centres()->attach($this->centre->id, ['homeCentre' => true]); + $cu->delete(); + + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.retire', ['id' => $cu->id])); + + $this->seeInDatabase('centre_centre_user', [ + 'centre_user_id' => $cu->id, + 'centre_id' => $this->centre->id, + ]); + } + + public function testRetiringAnActiveNonTrashedWorkerReturns404(): void + { + $cu = factory(CentreUser::class)->create(); + + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.retire', ['id' => $cu->id])) + ->assertResponseStatus(404); + } + + public function testRetiredWorkerCannotBeToggled(): void + { + $cu = factory(CentreUser::class)->create(); + $cu->centres()->attach($this->centre->id, ['homeCentre' => true]); + $cu->delete(); + $cu->retire(); + + $this->actingAs($this->adminUser, 'admin') + ->get(route('admin.centreusers.toggle', $cu->id)); + + // retired_at must still be set — the restoring event blocked the restore. + $this->assertNotNull(CentreUser::withTrashed()->find($cu->id)->retired_at); + } + + public function testRetiredWorkerDoesNotAppearOnIndex(): void + { + $cu = factory(CentreUser::class)->state('retired')->create(); + + $this->actingAs($this->adminUser, 'admin') + ->visit(route('admin.centreusers.index')) + ->assertResponseOk() + ->dontSee($cu->email); } } From 9c23252d673883166229bdfc0527d05a0b463817 Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 7 Apr 2026 16:16:16 +0100 Subject: [PATCH 050/168] feature: add migration for centre to flag can_collect feature --- ...160900_add_distribution_flag_to_centre.php | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 database/migrations/2026_04_07_160900_add_distribution_flag_to_centre.php diff --git a/database/migrations/2026_04_07_160900_add_distribution_flag_to_centre.php b/database/migrations/2026_04_07_160900_add_distribution_flag_to_centre.php new file mode 100644 index 000000000..3d9e1be7a --- /dev/null +++ b/database/migrations/2026_04_07_160900_add_distribution_flag_to_centre.php @@ -0,0 +1,27 @@ +boolean('can_collect')->after('print_pref')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('carers', static function (Blueprint $table) { + $table->dropColumn(['can_collect']); + }); + } +}; From 8609a71f32b0b0a8bc6c00b5e3fe877d2ab87a36 Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 7 Apr 2026 16:17:08 +0100 Subject: [PATCH 051/168] refactor: update the centre model to newer standards --- app/Centre.php | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/app/Centre.php b/app/Centre.php index 89816dadb..bd3b69696 100644 --- a/app/Centre.php +++ b/app/Centre.php @@ -4,7 +4,9 @@ use Eloquent; use Illuminate\Database\Eloquent\Model; - +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\belongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; /** * @mixin Eloquent @@ -36,7 +38,7 @@ class Centre extends Model protected $hidden = [ ]; - public function nextCentreSequence() + public function nextCentreSequence(): int { // Get the last family $last_family = $this->families()->orderByDesc('centre_sequence')->first(); @@ -46,7 +48,7 @@ public function nextCentreSequence() // Override it if the family has a sequence. if ($last_family && $last_family->centre_sequence) { - $sequence = $last_family->centre_sequence +1; + $sequence = $last_family->centre_sequence + 1; } return $sequence; @@ -54,48 +56,39 @@ public function nextCentreSequence() /** * Get the Registrations for this Centre - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function registrations() + public function registrations(): HasMany { - return $this->hasMany('App\Registration'); + return $this->hasMany(Registration::class); } /** * Get the CentreUsers who belong to this Centre - * - * @return \Illuminate\Database\Eloquent\Relations\belongsToMany */ - public function centreUsers() + public function centreUsers(): BelongsToMany { - return $this->belongsToMany('App\CentreUser'); + return $this->belongsToMany(CentreUser::class); } /** * Get the Sponsor for this Centre - * - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function sponsor() + public function sponsor(): BelongsTo { - return $this->belongsTo('App\Sponsor'); + return $this->belongsTo(Sponsor::class); } /** * Gets all the siblings under the same parent (including this one). * Self join; possible a better way to do this. - * - * @return \Illuminate\Database\Eloquent\Relations\HasMany */ - public function neighbours() + public function neighbours(): HasMany { - return $this->hasMany('App\Centre', 'sponsor_id', 'sponsor_id'); + return $this->hasMany(related: 'App\Centre', foreignKey: 'sponsor_id', localKey: 'sponsor_id'); } - public function families() + public function families(): HasMany { - return $this->hasMany('App\Family', 'initial_centre_id'); + return $this->hasMany(Family::class, 'initial_centre_id'); } - } From b6206f79cfc15dd7de43f1b5ab4ee29b77586b4b Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 7 Apr 2026 16:19:27 +0100 Subject: [PATCH 052/168] refactor: update self join to refer to the other centres as __class__ --- app/Centre.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Centre.php b/app/Centre.php index bd3b69696..22dbc6580 100644 --- a/app/Centre.php +++ b/app/Centre.php @@ -84,7 +84,7 @@ public function sponsor(): BelongsTo */ public function neighbours(): HasMany { - return $this->hasMany(related: 'App\Centre', foreignKey: 'sponsor_id', localKey: 'sponsor_id'); + return $this->hasMany(related: __CLASS__, foreignKey: 'sponsor_id', localKey: 'sponsor_id'); } public function families(): HasMany From 40f35950d0223e0f8f61b42528f9b2ef940cd2c9 Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 7 Apr 2026 16:20:23 +0100 Subject: [PATCH 053/168] feature: add can_collect bool to model --- app/Centre.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Centre.php b/app/Centre.php index 22dbc6580..5150f57e7 100644 --- a/app/Centre.php +++ b/app/Centre.php @@ -27,7 +27,7 @@ class Centre extends Model * @var array */ protected $fillable = [ - 'name', 'prefix', 'print_pref', 'sponsor_id' + 'name', 'prefix', 'print_pref', 'sponsor_id', 'can_collect' ]; /** From b0f85dee7bdb98a15faf22ec573368e1743cf302 Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 7 Apr 2026 16:24:34 +0100 Subject: [PATCH 054/168] feature: add collecting centre factory --- database/factories/ModelFactory.php | 6 ++++++ .../2026_04_07_160900_add_distribution_flag_to_centre.php | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 630d8f701..ffa69bd77 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -308,9 +308,15 @@ // print_pref will be 'collection' by default. // To ensure we always have one 'individual', adding to seeder as well. 'print_pref' => $faker->randomElement(['individual', 'collection']), + 'can_collect' => false ]; }); +$factory->state(App\Centre::class, 'collecting', function(Faker\Generator $faker) { + return ['can_collect' => true]; +}); + + // Registration $factory->define(App\Registration::class, function (Faker\Generator $faker, $attributes) { diff --git a/database/migrations/2026_04_07_160900_add_distribution_flag_to_centre.php b/database/migrations/2026_04_07_160900_add_distribution_flag_to_centre.php index 3d9e1be7a..59ce794f4 100644 --- a/database/migrations/2026_04_07_160900_add_distribution_flag_to_centre.php +++ b/database/migrations/2026_04_07_160900_add_distribution_flag_to_centre.php @@ -10,7 +10,7 @@ */ public function up(): void { - Schema::table('carers', static function (Blueprint $table) { + Schema::table('centres', static function (Blueprint $table) { $table->boolean('can_collect')->after('print_pref')->nullable(); }); } @@ -20,7 +20,7 @@ public function up(): void */ public function down(): void { - Schema::table('carers', static function (Blueprint $table) { + Schema::table('centres', static function (Blueprint $table) { $table->dropColumn(['can_collect']); }); } From 11709846f0744d3fd474a363d70b07ce9c4d22aa Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 7 Apr 2026 16:50:17 +0100 Subject: [PATCH 055/168] feature: add tests, tighten model migration --- app/Centre.php | 10 +++++++ ...160900_add_distribution_flag_to_centre.php | 2 +- tests/Unit/Models/CentreModelTest.php | 26 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/app/Centre.php b/app/Centre.php index 5150f57e7..a678e6d4e 100644 --- a/app/Centre.php +++ b/app/Centre.php @@ -13,6 +13,7 @@ * @property string $name * @property string $prefix * @property string $print_pref + * @property boolean $can_collect * @property Sponsor $sponsor * @property Registration[] $registrations * @property CentreUser[] $centreUsers @@ -38,6 +39,15 @@ class Centre extends Model protected $hidden = [ ]; + /** + * Casts + * + * @var array + */ + protected $casts = [ + 'can_collect' => 'boolean' + ]; + public function nextCentreSequence(): int { // Get the last family diff --git a/database/migrations/2026_04_07_160900_add_distribution_flag_to_centre.php b/database/migrations/2026_04_07_160900_add_distribution_flag_to_centre.php index 59ce794f4..842010ef1 100644 --- a/database/migrations/2026_04_07_160900_add_distribution_flag_to_centre.php +++ b/database/migrations/2026_04_07_160900_add_distribution_flag_to_centre.php @@ -11,7 +11,7 @@ public function up(): void { Schema::table('centres', static function (Blueprint $table) { - $table->boolean('can_collect')->after('print_pref')->nullable(); + $table->boolean('can_collect')->after('print_pref')->default(false); }); } diff --git a/tests/Unit/Models/CentreModelTest.php b/tests/Unit/Models/CentreModelTest.php index caa831a3f..d55097a8a 100644 --- a/tests/Unit/Models/CentreModelTest.php +++ b/tests/Unit/Models/CentreModelTest.php @@ -99,4 +99,30 @@ public function testItCanHaveNeighbours(): void $this->assertEquals($b_centres->pluck('id'), $bc->neighbours->pluck('id')); } } + + public function testItCanNotCollectByDefault(): void + { + $centre = factory(Centre::class)->create(); + $this->assertFalse($centre->can_collect); + } + + public function testItCanBeSetToCollect(): void + { + $centre = factory(Centre::class)->states('collecting')->create(); + $this->assertTrue($centre->can_collect); + } + + public function testCanCollectIsPersisted(): void + { + $centre = factory(Centre::class)->states('collecting')->create(); + $this->assertTrue(Centre::find($centre->id)->can_collect); + } + + public function testCanCollectCanBeToggledToFalse(): void + { + $centre = factory(Centre::class)->states('collecting')->create(); + $centre->can_collect = false; + $centre->save(); + $this->assertFalse(Centre::find($centre->id)->can_collect); + } } From d437792e8e67df6f5d164854f82fa932de022701 Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 8 Apr 2026 11:12:14 +0100 Subject: [PATCH 056/168] fix: asertSame instead of assertEquals --- tests/Unit/Models/CentreUserModelTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Unit/Models/CentreUserModelTest.php b/tests/Unit/Models/CentreUserModelTest.php index a208b4812..dae6ac20d 100644 --- a/tests/Unit/Models/CentreUserModelTest.php +++ b/tests/Unit/Models/CentreUserModelTest.php @@ -70,9 +70,9 @@ public function testCentreUserCanHaveAHomeCentre(): void $cu->centres()->attach($centre->id, ['homeCentre' => true]); // There is one - $this->assertEquals(1, $cu->centres()->count()); + $this->assertSame(1, $cu->centres()->count()); // It is the homeCentre - $this->assertEquals($centre->id, $cu->homeCentre->id); + $this->assertSame($centre->id, $cu->homeCentre->id); } @@ -87,7 +87,7 @@ public function testCentreUserCanHaveAlternativeCentres(): void $cu->centres()->attach($centres->pluck('id')->all()); // There is 4 - $this->assertEquals(4, $cu->centres()->count()); + $this->assertSame(4, $cu->centres()->count()); // But We have no homeCentre $this->assertEmpty($cu->homeCentre); @@ -172,7 +172,7 @@ public function testRetiredCentreUserRetainsCentreRelations(): void $this->centreUser->retire(); $fresh = CentreUser::withTrashed()->find($this->centreUser->id); - $this->assertEquals(1, $fresh->centres()->count()); + $this->assertSame(1, $fresh->centres()->count()); } public function testRetiredCentreUserCannotBeRestored(): void From 56daf547446c5aa7ea9bdc719a9b52aaf58f1ce8 Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 8 Apr 2026 11:35:11 +0100 Subject: [PATCH 057/168] fix: remove redundant check on deleted_at --- .../Unit/Controllers/Service/Admin/CentreUserControllerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/Controllers/Service/Admin/CentreUserControllerTest.php b/tests/Unit/Controllers/Service/Admin/CentreUserControllerTest.php index b2d605d95..2e7fe1637 100644 --- a/tests/Unit/Controllers/Service/Admin/CentreUserControllerTest.php +++ b/tests/Unit/Controllers/Service/Admin/CentreUserControllerTest.php @@ -137,7 +137,7 @@ public function testItCanDisableACentreUser(): void // Record is soft-deleted but still present. $this->assertNull(CentreUser::find($cu->id)); - $this->assertNotNull(CentreUser::withTrashed()->find($cu->id)->deleted_at); + $this->assertNotNull(CentreUser::withTrashed()->find($cu->id)); } public function testItCanEnableACentreUser(): void From 4256ff8975ffce6b9bf86c01c0fa9fccf247d412 Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 8 Apr 2026 12:11:57 +0100 Subject: [PATCH 058/168] fix: throw when we try to retire an active user, rather than plough ahead anyway --- app/Traits/Retirable.php | 9 ++++++--- database/factories/ModelFactory.php | 2 ++ tests/Unit/Models/CentreUserModelTest.php | 22 ++++++++++++---------- tests/Unit/Traits/RetirableTest.php | 23 +++++++++++++++++------ 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/app/Traits/Retirable.php b/app/Traits/Retirable.php index 230b7603a..f78679d40 100644 --- a/app/Traits/Retirable.php +++ b/app/Traits/Retirable.php @@ -54,12 +54,15 @@ public function retire(): void return; } + if (!$this->trashed()) { + throw new DomainException( + get_class($this) . " [$this->id] must be disabled before it can be retired." + ); + } + $this->forceFill( array_merge($this->retirableFields(), ['retired_at' => now()]) )->save(); - - // if we're not already soft deleted, do that too. - $this->delete(); } public function isRetired(): bool diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 899e243de..33faec707 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -63,6 +63,8 @@ * Relations (notes, centres) are preserved on the underlying row. */ $factory->afterCreatingState(App\CentreUser::class, 'retired', function ($centreUser) { + // retired centres must be soft deleted first + $centreUser->delete(); $centreUser->retire(); }); diff --git a/tests/Unit/Models/CentreUserModelTest.php b/tests/Unit/Models/CentreUserModelTest.php index a208b4812..8a832992f 100644 --- a/tests/Unit/Models/CentreUserModelTest.php +++ b/tests/Unit/Models/CentreUserModelTest.php @@ -70,9 +70,9 @@ public function testCentreUserCanHaveAHomeCentre(): void $cu->centres()->attach($centre->id, ['homeCentre' => true]); // There is one - $this->assertEquals(1, $cu->centres()->count()); + $this->assertSame(1, $cu->centres()->count()); // It is the homeCentre - $this->assertEquals($centre->id, $cu->homeCentre->id); + $this->assertSame($centre->id, $cu->homeCentre->id); } @@ -87,7 +87,7 @@ public function testCentreUserCanHaveAlternativeCentres(): void $cu->centres()->attach($centres->pluck('id')->all()); // There is 4 - $this->assertEquals(4, $cu->centres()->count()); + $this->assertSame(4, $cu->centres()->count()); // But We have no homeCentre $this->assertEmpty($cu->homeCentre); @@ -110,16 +110,16 @@ public function testCentreUserIsNotRetiredByDefault(): void public function testRetiredCentreUserHasNameCleared(): void { - $this->centreUser->retire(); + $cu = factory(CentreUser::class)->state('retired')->create()->fresh(); - $fresh = CentreUser::withTrashed()->find($this->centreUser->id); - $this->assertSame('[User Retired]', $fresh->name); + $this->assertSame('[User Retired]', $cu->name); } public function testRetiredCentreUserHasEmailReplacedWithSafeRetiredPlaceholder(): void { $originalEmail = $this->centreUser->email; // capture before retire() mutates the instance + $this->centreUser->delete(); $this->centreUser->retire(); $email = CentreUser::withTrashed()->find($this->centreUser->id)->email; @@ -136,6 +136,7 @@ public function testRetiredCentreUserHasPasswordReplacedWithANewHash(): void { $originalPassword = $this->centreUser->password; // capture before retire() mutates the instance + $this->centreUser->delete(); $this->centreUser->retire(); $replacedPassword = CentreUser::withTrashed()->find($this->centreUser->id)->password; @@ -146,10 +147,9 @@ public function testRetiredCentreUserHasPasswordReplacedWithANewHash(): void public function testRetiredCentreUserHasRememberTokenCleared(): void { - $this->centreUser->retire(); + $cu = factory(CentreUser::class)->state('retired')->create()->fresh(); - $fresh = CentreUser::withTrashed()->find($this->centreUser->id); - $this->assertNull($fresh->remember_token); + $this->assertNull($cu->remember_token); } public function testRetiredCentreUserRetainsNoteRelations(): void @@ -157,6 +157,7 @@ public function testRetiredCentreUserRetainsNoteRelations(): void // Notes exist before retirement. $this->assertCount(2, $this->centreUser->notes); + $this->centreUser->delete(); $this->centreUser->retire(); // Notes are still associated via FK after retirement. @@ -169,10 +170,11 @@ public function testRetiredCentreUserRetainsCentreRelations(): void $centre = factory(Centre::class)->create(); $this->centreUser->centres()->attach($centre->id, ['homeCentre' => true]); + $this->centreUser->delete(); $this->centreUser->retire(); $fresh = CentreUser::withTrashed()->find($this->centreUser->id); - $this->assertEquals(1, $fresh->centres()->count()); + $this->assertSame(1, $fresh->centres()->count()); } public function testRetiredCentreUserCannotBeRestored(): void diff --git a/tests/Unit/Traits/RetirableTest.php b/tests/Unit/Traits/RetirableTest.php index dbb755149..f42ef52e7 100644 --- a/tests/Unit/Traits/RetirableTest.php +++ b/tests/Unit/Traits/RetirableTest.php @@ -101,29 +101,31 @@ public function testRetiredAtIsAutomaticallyCastToDatetime(): void // ----------------------------------------------------------------------- - public function testRetireSetsRetiredAtToCurrentTime(): void + public function testRetireThrowsDomainExceptionIfModelIsNotSoftDeleted(): void { $stub = $this->makeStub(); - $stub->retire(); + $this->expectException(DomainException::class); + $this->expectExceptionMessage('must be disabled before it can be retired'); - $this->assertNotNull($stub->fresh()->retired_at); + $stub->retire(); } - - public function testRetireSoftDeletesTheModel(): void + public function testRetireSetsRetiredAtToCurrentTime(): void { $stub = $this->makeStub(); + $stub->delete(); $stub->retire(); - $this->assertSoftDeleted('retirable_stubs', ['id' => $stub->id]); + $this->assertNotNull($stub->fresh()->retired_at); } public function testRetireReplacesEachFieldDeclaredInRetirableFields(): void { $stub = $this->makeStub(); + $stub->delete(); $originalEmail = $stub->email; $originalPassword = $stub->password; @@ -140,6 +142,7 @@ public function testRetireReplacesEachFieldDeclaredInRetirableFields(): void public function testRetireStoresAValidEmailPlaceholder(): void { $stub = $this->makeStub(); + $stub->delete(); $stub->retire(); @@ -153,6 +156,7 @@ public function testRetireStoresAValidEmailPlaceholder(): void public function testRetireStoresAHashedPasswordNotPlainText(): void { $stub = $this->makeStub(); + $stub->delete(); $stub->retire(); @@ -165,6 +169,7 @@ public function testRetireStoresAHashedPasswordNotPlainText(): void public function testRetireIsIdempotentAndDoesNotChangeFieldsOnSecondCall(): void { $stub = $this->makeStub(); + $stub->delete(); $stub->retire(); $afterFirst = TestRetirableModel::withTrashed()->find($stub->id); @@ -185,7 +190,9 @@ public function testTwoSeparatelyRetiredModelsReceiveUniqueEmailPlaceholders(): $first = $this->makeStub(['email' => 'first@example.com']); $second = $this->makeStub(['email' => 'second@example.com']); + $first->delete(); $first->retire(); + $second->delete(); $second->retire(); $emailFirst = TestRetirableModel::withTrashed()->find($first->id)->email; @@ -210,6 +217,7 @@ public function testIsRetiredReturnsFalseForAnActiveModel(): void public function testIsRetiredReturnsTrueAfterRetirement(): void { $stub = $this->makeStub(); + $stub->delete(); $stub->retire(); @@ -224,6 +232,7 @@ public function testIsRetiredReturnsTrueAfterRetirement(): void public function testRestoringARetiredModelThrowsADomainException(): void { $stub = $this->makeStub(); + $stub->delete(); $stub->retire(); $this->expectException(DomainException::class); @@ -255,6 +264,7 @@ public function testScopeRetiredReturnsOnlyRetiredModels(): void $retired = $this->makeStub(['email' => 'retired@example.com']); $deleted->delete(); + $retired->delete(); $retired->retire(); $results = TestRetirableModel::retired()->get(); @@ -271,6 +281,7 @@ public function testScopeActiveExcludesRetiredAndSoftDeletedModels(): void $retired = $this->makeStub(['email' => 'retired@example.com']); $deleted->delete(); + $retired->delete(); $retired->retire(); $results = TestRetirableModel::active()->get(); From 480398892ddce308a4ce291b0fad77ddbd25e62b Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 8 Apr 2026 15:04:55 +0100 Subject: [PATCH 059/168] feature: modernaise and add can-collect checkbox to create page --- .../views/service/centres/create.blade.php | 117 ++++++++++++------ 1 file changed, 77 insertions(+), 40 deletions(-) diff --git a/resources/views/service/centres/create.blade.php b/resources/views/service/centres/create.blade.php index 9d45ca898..c047b0dc3 100644 --- a/resources/views/service/centres/create.blade.php +++ b/resources/views/service/centres/create.blade.php @@ -1,50 +1,87 @@ @extends('service.layouts.app') @section('content') -
- @include('service.includes.sidebar') -
+
+ @include('service.includes.sidebar') +
-

Add a Children's Centre

+

Add a Children's Centre

-

Use the form below to add a new children's centre. Add their name, RVID prefix, area and form.

+

Use the form below to add a new children's centre. Add their name, RVID prefix, area and form.

-
- {!! csrf_field() !!} -
-
- - - @include('service.partials.validationMessages', array('inputName' => 'name')) + + @csrf +
+
+ + + @include('service.partials.validationMessages', ['inputName' => 'name']) +
+
+ + + @include('service.partials.validationMessages', ['inputName' => 'rvid_prefix']) +
+
+ + + @include('service.partials.validationMessages', ['inputName' => 'sponsor']) +
+
+ + +
+
+ can_collect)) + > + + @include('service.partials.validationMessages', ['inputName' => 'can-collect']) +
-
- - - @include('service.partials.validationMessages', array('inputName' => 'rvid_prefix')) -
-
- - - @include('service.partials.validationMessages', array('inputName' => 'sponsor')) -
-
- - -
-
- - + + +
-
@endsection From 43338bfcf3484cd027370749e584be1ed1b6611a Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 8 Apr 2026 15:14:37 +0100 Subject: [PATCH 060/168] fix: create retired centreusers with the factory method. --- database/seeders/CentreUsersSeeder.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/database/seeders/CentreUsersSeeder.php b/database/seeders/CentreUsersSeeder.php index 416eab6f4..f6ba220da 100644 --- a/database/seeders/CentreUsersSeeder.php +++ b/database/seeders/CentreUsersSeeder.php @@ -107,14 +107,12 @@ private function seedDeletedUsers(): void */ private function seedRetiredUsers(): void { - factory(CentreUser::class, 2) + factory(CentreUser::class, 2)->state('retired') ->create() ->each(function (CentreUser $centreUser) { $centre = Centre::inRandomOrder()->first() ?? factory(Centre::class)->create(); - $centreUser->centres()->attach($centre->id, ['homeCentre' => true]); - $centreUser->retire(); }); } From 87978542713111fb2823c4d96ff0fff2848d4b5f Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 8 Apr 2026 15:24:50 +0100 Subject: [PATCH 061/168] refactor: add counting variable to a test --- tests/Unit/Models/CentreUserModelTest.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Models/CentreUserModelTest.php b/tests/Unit/Models/CentreUserModelTest.php index 8a832992f..e52741bbd 100644 --- a/tests/Unit/Models/CentreUserModelTest.php +++ b/tests/Unit/Models/CentreUserModelTest.php @@ -155,14 +155,15 @@ public function testRetiredCentreUserHasRememberTokenCleared(): void public function testRetiredCentreUserRetainsNoteRelations(): void { // Notes exist before retirement. - $this->assertCount(2, $this->centreUser->notes); + $notesCount = count($this->centreUser->notes); + $this->assertCount($notesCount, $this->centreUser->notes); $this->centreUser->delete(); $this->centreUser->retire(); // Notes are still associated via FK after retirement. $fresh = CentreUser::withTrashed()->find($this->centreUser->id); - $this->assertCount(2, $fresh->notes); + $this->assertCount($notesCount, $fresh->notes); } public function testRetiredCentreUserRetainsCentreRelations(): void From 7c5833268c41a8e2be37046c9f26b0f4e17e543c Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 8 Apr 2026 15:45:41 +0100 Subject: [PATCH 062/168] fix: make time based test accurate --- tests/Unit/Traits/RetirableTest.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Traits/RetirableTest.php b/tests/Unit/Traits/RetirableTest.php index f42ef52e7..1eb4cac02 100644 --- a/tests/Unit/Traits/RetirableTest.php +++ b/tests/Unit/Traits/RetirableTest.php @@ -113,12 +113,17 @@ public function testRetireThrowsDomainExceptionIfModelIsNotSoftDeleted(): void public function testRetireSetsRetiredAtToCurrentTime(): void { + // Freeze Time! + $now = Carbon::now(); + Carbon::setTestNow($now); + $stub = $this->makeStub(); $stub->delete(); - $stub->retire(); + $this->assertSame($now->toDateTimeString(), $stub->fresh()->retired_at->toDateTimeString()); - $this->assertNotNull($stub->fresh()->retired_at); + // reset time! + Carbon::setTestNow(); } From c23b8cecb49bfcb87007ea4852eb474a0e0ae401 Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 8 Apr 2026 16:22:14 +0100 Subject: [PATCH 063/168] fetaure: add checkbox to create page --- resources/assets/sass/app.scss | 12 +++++------ .../views/service/centres/create.blade.php | 20 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/resources/assets/sass/app.scss b/resources/assets/sass/app.scss index a49682d7e..2d8389248 100644 --- a/resources/assets/sass/app.scss +++ b/resources/assets/sass/app.scss @@ -96,7 +96,7 @@ h1 { .checkbox-group { position: relative; - padding-left: 55px; + padding-left: 2.5rem; margin: 0.5rem 0 2rem 0; min-height: 10px; @@ -107,20 +107,20 @@ h1 { &:checked + label:after { content: '✔'; position: absolute; - top: 6px; - left: 12px; + left: 0.75rem; + top: 0; border: 0; } } > label { cursor: pointer; - margin-left: -20px; + line-height: 2.5rem; &:before { content: ''; background: #ffffff; - width: 34px; - height: 34px; + width: 2.5rem; + height: 2.5rem; border: 1px solid #ececec; position: absolute; left: 0; diff --git a/resources/views/service/centres/create.blade.php b/resources/views/service/centres/create.blade.php index c047b0dc3..0c774ae71 100644 --- a/resources/views/service/centres/create.blade.php +++ b/resources/views/service/centres/create.blade.php @@ -68,16 +68,16 @@ class="@error('print_pref') error @enderror" @endforeach
-
- can_collect)) - > - - @include('service.partials.validationMessages', ['inputName' => 'can-collect']) -
+
+
+ + + @include('service.partials.validationMessages', ['inputName' => 'can-collect'])
From 306f53f0f2344894354d72216ee07bf917c10fa0 Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 8 Apr 2026 17:13:03 +0100 Subject: [PATCH 064/168] fetaure: update CentresController::store() and its FormRequest --- .../Service/Admin/CentresController.php | 123 +++++++----------- app/Http/Requests/AdminNewCentreRequest.php | 65 +++++---- .../views/service/centres/create.blade.php | 36 ++--- 3 files changed, 106 insertions(+), 118 deletions(-) diff --git a/app/Http/Controllers/Service/Admin/CentresController.php b/app/Http/Controllers/Service/Admin/CentresController.php index e3a6dd034..2b37244eb 100644 --- a/app/Http/Controllers/Service/Admin/CentresController.php +++ b/app/Http/Controllers/Service/Admin/CentresController.php @@ -6,96 +6,73 @@ use App\Http\Controllers\Controller; use App\Http\Requests\AdminNewCentreRequest; use App\Http\Requests\AdminUpdateCentreRequest; -use Auth; -use DB; -use Exception; -use Illuminate\Contracts\View\Factory; +use App\Sponsor; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; use Illuminate\View\View; -use App\Sponsor; -use Log; +use Throwable; class CentresController extends Controller { - /** * Display a listing of Centres. - * - * @return Factory|View */ - public function index() + public function index(): View { - $centres = Centre::get(); + $centres = Centre::all(); return view('service.centres.index', compact('centres')); } /** * Show the form for creating new Centres. - * - * @return Factory|View */ - public function create() + public function create(): View { - $sponsors = Sponsor::get(); + $sponsors = Sponsor::all(); return view('service.centres.create', compact('sponsors')); } /** - * Return a json list of neighbour names and IDs - * - * @param $id - * @return JsonResponse + * Return a JSON list of neighbour names and IDs. */ - public function getNeighboursAsJson($id) + public function getNeighboursAsJson(int $id): JsonResponse { try { - /** @var Centre $centre */ - $centre = Centre::findOrFail($id); - $neighbours = $centre + $neighbours = Centre::findOrFail($id) ->neighbours() ->whereKeyNot($id) - ->get(['name', 'id']) - ; - } catch (ModelNotFoundException $e) { - $neighbours = collect([]); + ->get(['name', 'id']); + } catch (ModelNotFoundException) { + $neighbours = collect(); } + return response()->json($neighbours); } /** - * @param AdminNewCentreRequest $request - * @return RedirectResponse - * @throws \Throwable + * Store a newly created Centre. */ - public function store(AdminNewCentreRequest $request) + public function store(AdminNewCentreRequest $request): RedirectResponse { try { - $centre = DB::transaction(function () use ($request) { - - // Create a Centre - $c = new Centre([ - 'name' => $request->input('name'), - 'prefix' => $request->input('rvid_prefix'), - 'print_pref' => $request->input('print_pref'), - 'sponsor_id' => $request->input('sponsor') - ]); - $c->save(); - - return $c; + $centre = DB::transaction(static function () use ($request): Centre { + return Centre::create($request->validated()); }); - } catch (Exception $e) { - // Oops! Log that + } catch (Throwable $e) { Log::error('Bad transaction for ' . __CLASS__ . '@' . __METHOD__ . ' by service user ' . Auth::id()); Log::error($e->getTraceAsString()); - // Throw it back to the user + return redirect() ->route('admin.centres.create') ->withErrors('Creation failed - DB Error.'); } + return redirect() ->route('admin.centres.index') ->with('message', 'Centre ' . $centre->name . ' created'); @@ -103,43 +80,37 @@ public function store(AdminNewCentreRequest $request) /** * Show the form for editing a Centre. - * - * @return Factory|View */ - public function edit($id) + public function edit(Centre $centre): View { - $centre = Centre::find($id); return view('service.centres.edit', compact('centre')); } /** - * Show the form for editing a Centre's name. - * - * @return Factory|View + * Update the specified Centre's name. */ - public function update(AdminUpdateCentreRequest $request, Centre $id) + public function update(AdminUpdateCentreRequest $request, Centre $centre): RedirectResponse { - try { - $centre = DB::transaction(function () use ($request, $id) { - // Update the system - $id->fill([ - 'name' => $request->input('name'), - ]); - $id->save(); - - return $id; - }); - } catch (Exception $e) { - // Oops! Log that - Log::error('Bad transaction for ' . __CLASS__ . '@' . __METHOD__ . ' by service user ' . Auth::id()); - Log::error($e->getTraceAsString()); - // Throw it back to the user - return redirect() - ->route('admin.centres.create') - ->withErrors('Creation failed - DB Error.'); - } - return redirect() - ->route('admin.centres.index') - ->with('message', 'Centre ' . $centre->name . ' edited'); + try { + $centre = DB::transaction(static function () use ($request, $centre): Centre { + $centre->fill([ + 'name' => $request->input('name'), + ]); + $centre->save(); + + return $centre; + }); + } catch (Throwable $e) { + Log::error('Bad transaction for ' . __CLASS__ . '@' . __METHOD__ . ' by service user ' . Auth::id()); + Log::error($e->getTraceAsString()); + + return redirect() + ->route('admin.centres.edit', $centre) + ->withErrors('Update failed - DB Error.'); + } + + return redirect() + ->route('admin.centres.index') + ->with('message', 'Centre ' . $centre->name . ' edited'); } } diff --git a/app/Http/Requests/AdminNewCentreRequest.php b/app/Http/Requests/AdminNewCentreRequest.php index c71743cb9..0c70983b8 100644 --- a/app/Http/Requests/AdminNewCentreRequest.php +++ b/app/Http/Requests/AdminNewCentreRequest.php @@ -4,7 +4,6 @@ use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; -use App\Rules\NotExistsRule; class AdminNewCentreRequest extends FormRequest { @@ -13,49 +12,67 @@ class AdminNewCentreRequest extends FormRequest * * @return bool */ - public function authorize() + public function authorize(): bool { // Covered by Admin user auth return true; } /** - * Get the validation rules that apply to the request. - * - * @return array + * Normalise input before validation runs. + */ + public function prepareForValidation(): void + { + $this->merge([ + 'can_collect' => $this->boolean('can_collect'), + 'prefix' => strtoupper((string)$this->input('prefix', '')), + ]); + } + + /** + * Validation rules */ - public function rules() + public function rules(): array { return [ - // MUST be present, string - 'name' => 'required|string', - // MUST be present, integer, in table - 'sponsor' => 'required|integer|exists:sponsors,id', - // MUST be present, string, 1-5 characters and not in use - 'rvid_prefix' => [ + 'name' => ['required', 'string'], + 'sponsor_id' => ['required', 'exists:sponsors,id'], + 'prefix' => [ 'required', 'string', 'between:1,5', - new NotExistsRule('centres', 'prefix'), + Rule::unique('centres', 'prefix'), ], - // MUST be present, in print_prefs 'print_pref' => [ 'required', - Rule::in(config('arc.print_preferences')) - ] + Rule::in(config('arc.print_preferences')), + ], + 'can_collect' => ['nullable', 'boolean'] + ]; + } + + /** + * Human-readable attribute names used in error messages. + */ + public function attributes(): array + { + return [ + 'sponsor_id' => 'sponsor', + 'prefix' => 'RVID prefix', + 'print_pref' => 'print preference', + 'can-collect' => 'collection', ]; } /** - * Prep input for validation + * Custom error messages. */ - public function prepareForValidation() + public function messages(): array { - if ($this->has('rvid_prefix')) { - $this->merge( - // In this system, we're want it uppercase - ['rvid_prefix' => strtoupper($this->input('rvid_prefix'))] - ); - } + return [ + 'prefix.unique' => 'That RVID prefix is already in use.', + 'prefix.between' => 'The RVID prefix must be between 1 and 5 characters.', + 'print_pref.in' => 'The selected print preference is not valid.', + ]; } } diff --git a/resources/views/service/centres/create.blade.php b/resources/views/service/centres/create.blade.php index 0c774ae71..4176c44cc 100644 --- a/resources/views/service/centres/create.blade.php +++ b/resources/views/service/centres/create.blade.php @@ -27,31 +27,31 @@ class="@error('name') error @enderror" @include('service.partials.validationMessages', ['inputName' => 'name'])
- + - @include('service.partials.validationMessages', ['inputName' => 'rvid_prefix']) + @include('service.partials.validationMessages', ['inputName' => 'prefix'])
- - @foreach ($sponsors as $sponsor) @endforeach - @include('service.partials.validationMessages', ['inputName' => 'sponsor']) + @include('service.partials.validationMessages', ['inputName' => 'sponsor_id'])
@@ -71,13 +71,13 @@ class="@error('print_pref') error @enderror"
- - @include('service.partials.validationMessages', ['inputName' => 'can-collect']) + + @include('service.partials.validationMessages', ['inputName' => 'can_collect'])
From ae1692643cf546c2b33680592ec9e7414e627c56 Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 9 Apr 2026 10:41:47 +0100 Subject: [PATCH 065/168] fix: routing and tests --- .../Service/Admin/CentresController.php | 2 +- routes/service.php | 4 +- .../Service/Admin/CentreControllerTest.php | 39 ++++---- .../AdminNewCentreRequestTest.php | 91 ++++++++++++++----- 4 files changed, 88 insertions(+), 48 deletions(-) diff --git a/app/Http/Controllers/Service/Admin/CentresController.php b/app/Http/Controllers/Service/Admin/CentresController.php index 2b37244eb..58bc4790a 100644 --- a/app/Http/Controllers/Service/Admin/CentresController.php +++ b/app/Http/Controllers/Service/Admin/CentresController.php @@ -105,7 +105,7 @@ public function update(AdminUpdateCentreRequest $request, Centre $centre): Redir Log::error($e->getTraceAsString()); return redirect() - ->route('admin.centres.edit', $centre) + ->route('admin.centres.edit', $centre->id) ->withErrors('Update failed - DB Error.'); } diff --git a/routes/service.php b/routes/service.php index dbc9dbae5..0d63c448f 100644 --- a/routes/service.php +++ b/routes/service.php @@ -143,10 +143,10 @@ 'as' => 'admin.centres.store', 'uses' => 'Admin\CentresController@store', ]); - Route::put('centres/{id}/update', [ + Route::put('centres/{centre}/update', [ 'as' => 'admin.centres.update', 'uses' => 'Admin\CentresController@update', - ])->where('id', '^[0-9]+$'); + ])->where('centre', '^[0-9]+$'); Route::get('centres/{id}/edit', [ 'as' => 'admin.centres.edit', 'uses' => 'Admin\CentresController@edit', diff --git a/tests/Unit/Controllers/Service/Admin/CentreControllerTest.php b/tests/Unit/Controllers/Service/Admin/CentreControllerTest.php index ffddbe268..c23bab60a 100644 --- a/tests/Unit/Controllers/Service/Admin/CentreControllerTest.php +++ b/tests/Unit/Controllers/Service/Admin/CentreControllerTest.php @@ -14,17 +14,13 @@ class CentreControllerTest extends StoreTestCase { use RefreshDatabase; - /** @var AdminUser $adminUser */ - private $adminUser; + private AdminUser $adminUser; - /** @var Sponsor $sponsor */ - private $sponsor; + private Sponsor $sponsor; - /** @var array $data */ - private $data; + private array $data; - /** @var Generator $faker */ - private $faker; + private Generator $faker; public function setUp(): void { @@ -34,9 +30,10 @@ public function setUp(): void $this->sponsor = factory(Sponsor::class)->create(); $this->data = [ 'name' => $this->faker->city, - 'sponsor' => $this->sponsor->id, - 'rvid_prefix' => strtoupper($this->faker->lexify(str_repeat('?', rand(1, 5)))), - 'print_pref' => array_random(config('arc.print_preferences')) + 'sponsor_id' => $this->sponsor->id, + 'prefix' => strtoupper($this->faker->lexify(str_repeat('?', random_int(1, 5)))), + 'print_pref' => array_random(config('arc.print_preferences')), + 'can_collect' => random_int(0, 1) ]; } @@ -53,18 +50,18 @@ public function testItCanStoreACentre(): void ->seePageIs(route('admin.centres.index')) ->see($this->data["name"]) ->see($this->sponsor->name) - ->see($this->data["rvid_prefix"]) + ->see($this->data["prefix"]) ->see($this->data["print_pref"]) ; // find the centre by prefix - $c = Centre::where('prefix', $this->data['rvid_prefix'])->first(); + $c = Centre::where('prefix', $this->data['prefix'])->first(); $this->assertNotNull($c); } public function testICanSeeAnEditButtonOnTheListOfCentres(): void { - $centre = factory(Centre::class)->create([]); + $centre = factory(Centre::class)->create(); $this->actingAs($this->adminUser, 'admin') ->get(route('admin.centres.index')) ->assertResponseOk() @@ -74,21 +71,21 @@ public function testICanSeeAnEditButtonOnTheListOfCentres(): void ; } - public function testICanUpdateACentreName(): void { - $centre = factory(Centre::class)->create([]); - $data = [ - 'id' => $centre->id, - 'name' => 'New Centre Name' - ]; + $centre = factory(Centre::class)->create(); $this->seeInDatabase('centres', [ 'id' => $centre->id, 'name' => $centre->name ]); + + $data = [ + 'id' => $centre->id, + 'name' => 'New Centre Name', + ]; $this->actingAs($this->adminUser, 'admin') ->put( - route('admin.centres.update', ['id' => $centre->id]), + route('admin.centres.update', ['centre' => $centre->id]), $data ); $this->seeInDatabase('centres', [ diff --git a/tests/Unit/FormRequests/AdminNewCentreRequestTest.php b/tests/Unit/FormRequests/AdminNewCentreRequestTest.php index b262f8c32..46f5f0bb3 100644 --- a/tests/Unit/FormRequests/AdminNewCentreRequestTest.php +++ b/tests/Unit/FormRequests/AdminNewCentreRequestTest.php @@ -41,89 +41,132 @@ public static function validationCases(): Generator { yield 'Valid request' => [true, [ 'name' => 'Test Centre', - 'sponsor' => 1, - 'rvid_prefix' => 'TSTCT', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', 'print_pref' => 'individual', + 'can_collect' => false, ]]; yield 'Missing name' => [false, [ - 'sponsor' => 1, - 'rvid_prefix' => 'TSTCT', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', 'print_pref' => 'individual', + 'can_collect' => false, ]]; yield 'Name is not a string' => [false, [ 'name' => 1, - 'sponsor' => 1, - 'rvid_prefix' => 'TSTCT', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', 'print_pref' => 'individual', + 'can_collect' => false, ]]; yield 'Missing sponsor' => [false, [ 'name' => 'Test Centre', - 'rvid_prefix' => 'TSTCT', + 'prefix' => 'TSTCT', 'print_pref' => 'individual', + 'can_collect' => false, ]]; yield 'Sponsor is not an integer' => [false, [ 'name' => 'Test Centre', - 'sponsor' => 'not an integer', - 'rvid_prefix' => 'TSTCT', + 'sponsor_id' => 'not an integer', + 'prefix' => 'TSTCT', 'print_pref' => 'individual', + 'can_collect' => false, ]]; yield 'Invalid sponsor' => [false, [ 'name' => 'Test Centre', - 'sponsor' => 999, - 'rvid_prefix' => 'TSTCT', + 'sponsor_id' => 999, + 'prefix' => 'TSTCT', 'print_pref' => 'individual', + 'can_collect' => false, ]]; yield 'Missing RVID prefix' => [false, [ 'name' => 'Test Centre', - 'sponsor' => 1, + 'sponsor_id' => 1, 'print_pref' => 'individual', + 'can_collect' => false, ]]; yield 'RVID is not a string' => [false, [ 'name' => 'Test Centre', - 'sponsor' => 1, - 'rvid_prefix' => 1, + 'sponsor_id' => 1, + 'prefix' => 1, 'print_pref' => 'individual', ]]; yield 'RVID is less than one character' => [false, [ 'name' => 'Test Centre', - 'sponsor' => 1, - 'rvid_prefix' => '', + 'sponsor_id' => 1, + 'prefix' => '', 'print_pref' => 'individual', + 'can_collect' => false, ]]; yield 'RVID is more than five characters' => [false, [ 'name' => 'Test Centre', - 'sponsor' => 1, - 'rvid_prefix' => 'ABCDEF', + 'sponsor_id' => 1, + 'prefix' => 'ABCDEF', 'print_pref' => 'individual', + 'can_collect' => false, ]]; yield 'RVID already exists' => [false, [ 'name' => 'Test Centre', - 'sponsor' => 1, - 'rvid_prefix' => 'EXISTS', + 'sponsor_id' => 1, + 'prefix' => 'EXISTS', 'print_pref' => 'not even slightly a print pref', + 'can_collect' => false, ]]; yield 'Missing print preference' => [false, [ 'name' => 'Test Centre', - 'sponsor' => 1, - 'rvid_prefix' => 'ABCDEF', + 'sponsor_id' => 1, + 'prefix' => 'ABCDEF', + 'can_collect' => false, ]]; yield 'Invalid print preference' => [false, [ 'name' => 'Test Centre', - 'sponsor' => 1, - 'rvid_prefix' => 'ABCDEF', + 'sponsor_id' => 1, + 'prefix' => 'ABCDEF', 'print_pref' => 'not even slightly a print pref', + 'can_collect' => false, + ]]; + + yield 'can_collect can be true' => [true, [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => true, + ]]; + + yield 'can_collect might be absent' => [true, [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + ]]; + + yield 'can_collect might be null' => [true, [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => null, + ]]; + + yield 'can_collect must be boolean' => [false, [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => 'true', ]]; } } From d3b205210c47cd3f9da9de73f7458476cb594c1d Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 9 Apr 2026 12:14:54 +0100 Subject: [PATCH 066/168] fix: eitehr/or the email / telno secrets --- .../Service/Data/FamilyContactsController.php | 2 +- .../Service/FamilyContactsControllerTest.php | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/Service/Data/FamilyContactsController.php b/app/Http/Controllers/Service/Data/FamilyContactsController.php index b2df7bfe3..0f60fa79a 100644 --- a/app/Http/Controllers/Service/Data/FamilyContactsController.php +++ b/app/Http/Controllers/Service/Data/FamilyContactsController.php @@ -21,7 +21,7 @@ public function __invoke(Request $request): StreamedResponse Carer::query() ->whereNotNull('emailsecret') - ->whereNotNull('telnosecret') + ->orWhereNotNull('telnosecret') ->with(['family.initialCentre.sponsor']) ->lazyById(200) ->chunk(200) diff --git a/tests/Feature/Service/FamilyContactsControllerTest.php b/tests/Feature/Service/FamilyContactsControllerTest.php index 7b25d1c1f..42f6041de 100644 --- a/tests/Feature/Service/FamilyContactsControllerTest.php +++ b/tests/Feature/Service/FamilyContactsControllerTest.php @@ -110,22 +110,22 @@ public function testEmptyResultSetReturnsHeaderOnly(): void $this->assertCount(1, $rows); } - public function testExcludesCarerWithNullEmailsecret(): void + public function testIncludesCarerWithNullEmailsecretButTelnoSecret(): void { $this->createQualifyingCarer(['emailsecret' => null]); $rows = $this->parseCsv($this->makeRequest()->streamedContent()); - $this->assertCount(1, $rows, 'Expected header row only — carer should be excluded.'); + $this->assertCount(2, $rows, 'Expected header row + 1 data row.'); } - public function testExcludesCarerWithNullTelnosecret(): void + public function testIncludesCarerWithNullTelnosecretButEmailsecret(): void { $this->createQualifyingCarer(['telnosecret' => null]); $rows = $this->parseCsv($this->makeRequest()->streamedContent()); - $this->assertCount(1, $rows, 'Expected header row only — carer should be excluded.'); + $this->assertCount(2, $rows, 'Expected header row + 1 data row.'); } public function testExcludesCarerWithBothSecretsNull(): void @@ -179,13 +179,14 @@ public function testMultipleQualifyingCarersAllAppear(): void public function testQualifyingAndNonQualifyingCarersMixed(): void { $this->createQualifyingCarer(['name' => 'Included']); - $this->createQualifyingCarer(['name' => 'Excluded — no email', 'emailsecret' => null]); - $this->createQualifyingCarer(['name' => 'Excluded — no telno', 'telnosecret' => null]); + $this->createQualifyingCarer(['name' => 'Included — no email', 'emailsecret' => null]); + $this->createQualifyingCarer(['name' => 'Included — no telno', 'telnosecret' => null]); + $this->createQualifyingCarer(['name' => 'Excluded — neither', 'emailsecret' => null, 'telnosecret' => null]); $rows = $this->parseCsv($this->makeRequest()->streamedContent()); $dataRows = array_slice($rows, 1); - $this->assertCount(1, $dataRows); - $this->assertSame('Included', $dataRows[0][1]); + $this->assertCount(3, $dataRows); + $this->assertNotContains('Excluded - neither', array_column($dataRows, 1)); } } From 26f48aed3138278c6f23d3c69cb32b96f1a1fe61 Mon Sep 17 00:00:00 2001 From: charlesstrange2 <25037036+charlesstrange2@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:19:44 +0100 Subject: [PATCH 067/168] Update resources/assets/sass/app.scss Co-authored-by: Colin Stewart --- resources/assets/sass/app.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/assets/sass/app.scss b/resources/assets/sass/app.scss index 9685922f9..98e3560a4 100644 --- a/resources/assets/sass/app.scss +++ b/resources/assets/sass/app.scss @@ -398,7 +398,7 @@ button.remove { flex: 0 0 300px; font-weight: 600; background-color: $arc_white; - border-right: 1px solid #ededed; + border-right: 1px solid $arc_border; ul { padding-left: 0; From 70c7275b5ffc9e3c4ff9d77f652a278f969071af Mon Sep 17 00:00:00 2001 From: charlesstrange2 <25037036+charlesstrange2@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:24:14 +0100 Subject: [PATCH 068/168] Update resources/assets/sass/app.scss Co-authored-by: Colin Stewart --- resources/assets/sass/app.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/assets/sass/app.scss b/resources/assets/sass/app.scss index 98e3560a4..5eb9ba97f 100644 --- a/resources/assets/sass/app.scss +++ b/resources/assets/sass/app.scss @@ -410,7 +410,7 @@ button.remove { padding: 1em 2em; &:hover, &:active { - background-color: #f5f5f5; + background-color: $arc_gray; } } From 61ed6924127abb1179eb4407e6d1acdd74b574a8 Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 9 Apr 2026 13:45:04 +0100 Subject: [PATCH 069/168] fix: put store login behind guard --- routes/store.php | 8 ++++++-- tests/Unit/Routes/StoreRoutesTest.php | 19 +++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/routes/store.php b/routes/store.php index 1facfbd41..d23944270 100644 --- a/routes/store.php +++ b/routes/store.php @@ -21,7 +21,6 @@ // Authentication Route::get('login', [LoginController::class, 'showLoginForm'])->name('store.login'); Route::post('login', [LoginController::class, 'login']); -Route::post('logout', [LoginController::class, 'logout'])->name('store.logout'); // Password Reset Route::get('password/reset', [ForgotPasswordController::class, 'showLinkRequestForm']) @@ -34,11 +33,16 @@ Route::post('password/reset', [ResetPasswordController::class, 'reset']); // Base redirect -Route::get('/', static fn () => redirect()->route('store.login'))->name('store.base'); +Route::get('/', static function () { + return redirect()->route('store.login'); +})->name('store.base'); // Authenticated routes Route::middleware('auth:store')->group(function () { + Route::post('logout', [LoginController::class, 'logout']) + ->name('store.logout'); + Route::get('dashboard', [DashboardController::class, 'index']) ->name('store.dashboard'); diff --git a/tests/Unit/Routes/StoreRoutesTest.php b/tests/Unit/Routes/StoreRoutesTest.php index 753a5d5b5..12297bd59 100644 --- a/tests/Unit/Routes/StoreRoutesTest.php +++ b/tests/Unit/Routes/StoreRoutesTest.php @@ -107,10 +107,17 @@ public function setUp(): void */ public function testLoginGuestRoute(): void { + $this->actingAs($this->centreUser, 'store') + ->post(URL::route('store.logout')) + ->followRedirects() + ->seePageIs(URL::route('store.login')) + ->dontSee('Sorry, there was a permission problem. Please Log in.'); + Auth::logout(); - $this->get(URL::route('store.login')) + $this->post(URL::route('store.logout')) + ->followRedirects() ->seePageIs(URL::route('store.login')) - ->assertResponseStatus(200); + ->see('Sorry, there was a permission problem. Please Log in.'); } /** @@ -126,6 +133,14 @@ public function testForgotPasswordGuestRoute(): void ->assertResponseStatus(200); } + public function testLogoutRouteGate(): void + { + Auth::logout(); + $this->visit($this->logoutRoute) + ->seePageIs(URL::route('store.logoin')) + ->assertResponseStatus(200); + } + public function testDashboardRouteGate(): void { From 238140bdca30224322647d07845c88a5c5da9032 Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 9 Apr 2026 16:54:22 +0100 Subject: [PATCH 070/168] feature: fix up the shonky edit page for consistency, update teh formrequests, and fix a bunch of tests --- .../Service/Admin/CentresController.php | 29 +- app/Http/Requests/AdminNewCentreRequest.php | 5 +- .../Requests/AdminUpdateCentreRequest.php | 72 +++- .../views/service/centres/create.blade.php | 2 +- .../views/service/centres/edit.blade.php | 120 +++++-- .../views/service/centres/index.blade.php | 2 +- routes/service.php | 8 +- .../AdminNewCentreRequestTest.php | 14 +- .../AdminUpdateCentreRequestTest.php | 333 ++++++++++++++++++ 9 files changed, 507 insertions(+), 78 deletions(-) create mode 100644 tests/Unit/FormRequests/AdminUpdateCentreRequestTest.php diff --git a/app/Http/Controllers/Service/Admin/CentresController.php b/app/Http/Controllers/Service/Admin/CentresController.php index 58bc4790a..6ce2cb3c0 100644 --- a/app/Http/Controllers/Service/Admin/CentresController.php +++ b/app/Http/Controllers/Service/Admin/CentresController.php @@ -7,7 +7,6 @@ use App\Http\Requests\AdminNewCentreRequest; use App\Http\Requests\AdminUpdateCentreRequest; use App\Sponsor; -use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Auth; @@ -41,16 +40,12 @@ public function create(): View /** * Return a JSON list of neighbour names and IDs. */ - public function getNeighboursAsJson(int $id): JsonResponse + public function getNeighboursAsJson(Centre $centre): JsonResponse { - try { - $neighbours = Centre::findOrFail($id) - ->neighbours() - ->whereKeyNot($id) - ->get(['name', 'id']); - } catch (ModelNotFoundException) { - $neighbours = collect(); - } + $neighbours = $centre + ->neighbours() + ->whereKeyNot($centre->getKey()) + ->get(['name', 'id']); return response()->json($neighbours); } @@ -83,22 +78,18 @@ public function store(AdminNewCentreRequest $request): RedirectResponse */ public function edit(Centre $centre): View { - return view('service.centres.edit', compact('centre')); + $sponsors = Sponsor::all(); + return view('service.centres.edit', compact('centre', 'sponsors')); } /** - * Update the specified Centre's name. + * Update the specified Centre's fields */ public function update(AdminUpdateCentreRequest $request, Centre $centre): RedirectResponse { try { - $centre = DB::transaction(static function () use ($request, $centre): Centre { - $centre->fill([ - 'name' => $request->input('name'), - ]); - $centre->save(); - - return $centre; + DB::transaction(static function () use ($request, $centre): bool { + return $centre->update($request->validated()); }); } catch (Throwable $e) { Log::error('Bad transaction for ' . __CLASS__ . '@' . __METHOD__ . ' by service user ' . Auth::id()); diff --git a/app/Http/Requests/AdminNewCentreRequest.php b/app/Http/Requests/AdminNewCentreRequest.php index 0c70983b8..33c5d0e3d 100644 --- a/app/Http/Requests/AdminNewCentreRequest.php +++ b/app/Http/Requests/AdminNewCentreRequest.php @@ -35,13 +35,13 @@ public function prepareForValidation(): void public function rules(): array { return [ - 'name' => ['required', 'string'], + 'name' => ['required', 'string', 'unique:centres,name'], 'sponsor_id' => ['required', 'exists:sponsors,id'], 'prefix' => [ 'required', 'string', 'between:1,5', - Rule::unique('centres', 'prefix'), + 'unique:centres,prefix', ], 'print_pref' => [ 'required', @@ -70,6 +70,7 @@ public function attributes(): array public function messages(): array { return [ + 'name.unique' => 'That name is already in use.', 'prefix.unique' => 'That RVID prefix is already in use.', 'prefix.between' => 'The RVID prefix must be between 1 and 5 characters.', 'print_pref.in' => 'The selected print preference is not valid.', diff --git a/app/Http/Requests/AdminUpdateCentreRequest.php b/app/Http/Requests/AdminUpdateCentreRequest.php index 4a7a64055..ae10fa81c 100644 --- a/app/Http/Requests/AdminUpdateCentreRequest.php +++ b/app/Http/Requests/AdminUpdateCentreRequest.php @@ -3,29 +3,79 @@ namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rule; class AdminUpdateCentreRequest extends FormRequest { /** * Determine if the user is authorized to make this request. - * - * @return bool */ - public function authorize() + public function authorize(): bool { return true; } /** - * Get the validation rules that apply to the request. - * - * @return array + * Normalise input before validation runs. */ - public function rules() + public function prepareForValidation(): void { - return [ - 'id' => 'required|integer|exists:centres,id', - 'name' => 'required|string', - ]; + $this->merge([ + 'can_collect' => $this->boolean('can_collect'), + 'prefix' => strtoupper((string)$this->input('prefix', '')), + ]); + } + + /** + * Validation rules + */ + public function rules(): array + { + $centreId = $this->route('centre')?->id; + return [ + 'name' => [ + 'required', + 'string', + Rule::unique('centres', 'name')->ignore($centreId), + ], + 'sponsor_id' => ['required', 'exists:sponsors,id'], + 'prefix' => [ + 'required', + 'string', + 'between:1,5', + Rule::unique('centres', 'prefix')->ignore($centreId), + ], + 'print_pref' => [ + 'required', + Rule::in(config('arc.print_preferences')), + ], + 'can_collect' => ['nullable', 'boolean'] + ]; + } + + /** + * Human-readable attribute names used in error messages. + */ + public function attributes(): array + { + return [ + 'sponsor_id' => 'sponsor', + 'prefix' => 'RVID prefix', + 'print_pref' => 'print preference', + 'can-collect' => 'collection', + ]; + } + + /** + * Custom error messages. + */ + public function messages(): array + { + return [ + 'name.unique' => 'That name is already in use.', + 'prefix.unique' => 'That RVID prefix is already in use.', + 'prefix.between' => 'The RVID prefix must be between 1 and 5 characters.', + 'print_pref.in' => 'The selected print preference is not valid.', + ]; } } diff --git a/resources/views/service/centres/create.blade.php b/resources/views/service/centres/create.blade.php index 4176c44cc..7414ef4fd 100644 --- a/resources/views/service/centres/create.blade.php +++ b/resources/views/service/centres/create.blade.php @@ -7,7 +7,7 @@

Add a Children's Centre

-

Use the form below to add a new children's centre. Add their name, RVID prefix, area and form.

+

Use the form below to add a new children's centre. Add their name, RVID prefix, area, form style and collecting state

- @include('service.includes.sidebar') -
-

{{ $centre->name }}

- @if (Session::get('message')) -
- {{ Session::get('message') }} -
- @endif - - @method('put') - @csrf - - - - - - - - - - - - - - - - - - - - -
NameRVID PrefixAreaFormSave
- {{ $centre->prefix }}{{ $centre->sponsor->name }}{{ ucwords($centre->print_pref) }}
- +
+ @include('service.includes.sidebar') +
+ +

Edit Children's Centre

+ +

Use the form below to edit this children's centre. Update their name, RVID prefix, area, form style and collecting state

+ +
+ @csrf + @method('PUT') +
+
+ + + @include('service.partials.validationMessages', ['inputName' => 'name']) +
+
+ + + @include('service.partials.validationMessages', ['inputName' => 'prefix']) +
+
+ + + @include('service.partials.validationMessages', ['inputName' => 'sponsor_id']) +
+
+ + +
+
+
+ can_collect)) + > + + @include('service.partials.validationMessages', ['inputName' => 'can_collect']) +
+ +
+
-
+ @endsection diff --git a/resources/views/service/centres/index.blade.php b/resources/views/service/centres/index.blade.php index 06060ab1b..00c1460cd 100644 --- a/resources/views/service/centres/index.blade.php +++ b/resources/views/service/centres/index.blade.php @@ -30,7 +30,7 @@ {{ $centre->sponsor->name }} {{ ucwords($centre->print_pref) }} - + Edit diff --git a/routes/service.php b/routes/service.php index 0d63c448f..b132dfb4f 100644 --- a/routes/service.php +++ b/routes/service.php @@ -135,10 +135,10 @@ 'as' => 'admin.centres.create', 'uses' => 'Admin\CentresController@create', ]); - Route::get('centres/{id}/neighbours', [ + Route::get('centres/{centre}/neighbours', [ 'as' => 'admin.centre_neighbours.index', 'uses' => 'Admin\CentresController@getNeighboursAsJson' - ])->where('id', '^[0-9]+$'); + ])->where('centre', '^[0-9]+$'); Route::post('centres', [ 'as' => 'admin.centres.store', 'uses' => 'Admin\CentresController@store', @@ -147,10 +147,10 @@ 'as' => 'admin.centres.update', 'uses' => 'Admin\CentresController@update', ])->where('centre', '^[0-9]+$'); - Route::get('centres/{id}/edit', [ + Route::get('centres/{centre}/edit', [ 'as' => 'admin.centres.edit', 'uses' => 'Admin\CentresController@edit', - ])->where('id', '^[0-9]+$'); + ])->where('centre', '^[0-9]+$'); // Sponsor Management Route::get('sponsors', [ diff --git a/tests/Unit/FormRequests/AdminNewCentreRequestTest.php b/tests/Unit/FormRequests/AdminNewCentreRequestTest.php index 46f5f0bb3..eacb99383 100644 --- a/tests/Unit/FormRequests/AdminNewCentreRequestTest.php +++ b/tests/Unit/FormRequests/AdminNewCentreRequestTest.php @@ -22,7 +22,7 @@ protected function setUp(): void parent::setUp(); $this->rules = (new AdminNewCentreRequest())->rules(); - factory(Centre::class)->create(['prefix' => 'EXISTS']); + factory(Centre::class)->create(['name' => 'EXIST', 'prefix' => 'EXIST']); factory(Sponsor::class)->create(); } @@ -62,6 +62,14 @@ public static function validationCases(): Generator 'can_collect' => false, ]]; + yield 'Name already exists' => [false, [ + 'name' => 'EXIST', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => false, + ]]; + yield 'Missing sponsor' => [false, [ 'name' => 'Test Centre', 'prefix' => 'TSTCT', @@ -118,8 +126,8 @@ public static function validationCases(): Generator yield 'RVID already exists' => [false, [ 'name' => 'Test Centre', 'sponsor_id' => 1, - 'prefix' => 'EXISTS', - 'print_pref' => 'not even slightly a print pref', + 'prefix' => 'EXIST', + 'print_pref' => 'individual', 'can_collect' => false, ]]; diff --git a/tests/Unit/FormRequests/AdminUpdateCentreRequestTest.php b/tests/Unit/FormRequests/AdminUpdateCentreRequestTest.php new file mode 100644 index 000000000..3b1e3bde2 --- /dev/null +++ b/tests/Unit/FormRequests/AdminUpdateCentreRequestTest.php @@ -0,0 +1,333 @@ +rules = (new AdminUpdateCentreRequest())->rules(); + factory(Centre::class)->create(['name' => 'EXIST', 'prefix' => 'EXIST']); + factory(Sponsor::class)->create(); + } + + private function validate(array $mockedRequestData): bool + { + return Validator::make($mockedRequestData, $this->rules)->passes(); + } + + private function validateAsUpdate(Centre $centre, array $data): bool + { + $request = new AdminUpdateCentreRequest(); + $request->setRouteResolver(function () use ($centre) { + return new class ($centre) { + public function __construct(private readonly Centre $centre) + { + } + + public function parameter(string $name): mixed + { + return $name === 'centre' ? $this->centre : null; + } + }; + }); + + return Validator::make($data, $request->rules())->passes(); + } + + + #[DataProvider('validationCases')] + public function testItValidatesCentreRequests( + bool $shouldPass, + array $mockedRequestData, + ?Closure $centreFn = null + ): void { + if ($centreFn) { + $centre = $centreFn(); + $this->assertEquals($shouldPass, $this->validateAsUpdate($centre, $mockedRequestData)); + } else { + $this->assertEquals($shouldPass, $this->validate($mockedRequestData)); + } + } + + public static function validationCases(): Generator + { + yield 'Valid request' => [ + true, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + ]; + + yield 'Missing name' => [ + false, + [ + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + ]; + + yield 'Name is not a string' => [ + false, + [ + 'name' => 1, + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + ]; + + yield 'Name already exists' => [ + false, + [ + 'name' => 'EXIST', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + ]; + + yield 'Missing sponsor' => [ + false, + [ + 'name' => 'Test Centre', + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + ]; + + yield 'Sponsor is not an integer' => [ + false, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 'not an integer', + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + ]; + + yield 'Invalid sponsor' => [ + false, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 999, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + ]; + + yield 'Missing RVID prefix' => [ + false, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'print_pref' => 'individual', + 'can_collect' => false, + ], + ]; + + yield 'RVID is not a string' => [ + false, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 1, + 'print_pref' => 'individual', + ], + ]; + + yield 'RVID is less than one character' => [ + false, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => '', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + ]; + + yield 'RVID is more than five characters' => [ + false, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 'ABCDEF', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + ]; + + yield 'RVID already exists' => [ + false, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 'EXISTS', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + ]; + + yield 'Missing print preference' => [ + false, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 'ABCDEF', + 'can_collect' => false, + ], + ]; + + yield 'Invalid print preference' => [ + false, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 'ABCDEF', + 'print_pref' => 'not even slightly a print pref', + 'can_collect' => false, + ], + ]; + + yield 'can_collect can be true' => [ + true, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => true, + ], + ]; + + yield 'can_collect might be absent' => [ + true, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + ], + ]; + + yield 'can_collect might be null' => [ + true, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => null, + ], + ]; + + yield 'can_collect must be boolean' => [ + false, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => 'true', + ], + ]; + + yield 'Centre can keep its own name' => [ + true, + [ + 'name' => 'EXIST', + 'sponsor_id' => 1, + 'prefix' => 'TSTCT', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + function () { + return Centre::where('name', 'EXIST')->first(); + }, + ]; + + yield 'Centre can keep its own prefix' => [ + true, + [ + 'name' => 'Test Centre', + 'sponsor_id' => 1, + 'prefix' => 'EXIST', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + function () { + return Centre::where('prefix', 'EXIST')->first(); + }, + ]; + + yield 'Centre can keep both its own name and prefix' => [ + true, + [ + 'name' => 'EXIST', + 'sponsor_id' => 1, + 'prefix' => 'EXIST', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + function () { + return Centre::where('name', 'EXIST')->first(); + }, + ]; + + yield 'Another centres name is still rejected' => [ + false, + [ + 'name' => 'EXIST', + 'sponsor_id' => 1, + 'prefix' => 'OTHER', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + function () { + return factory(Centre::class)->create(['name' => 'Other Centre', 'prefix' => 'OTHER']); + }, + ]; + + yield 'Another centres prefix is still rejected' => [ + false, + [ + 'name' => 'Other Centre', + 'sponsor_id' => 1, + 'prefix' => 'EXIST', + 'print_pref' => 'individual', + 'can_collect' => false, + ], + function () { + return factory(Centre::class)->create(['name' => 'Other Centre', 'prefix' => 'OTHER']); + }, + ]; + } +} From d9f7c548b802e7a02cef6e880d8e832d6d962694 Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 9 Apr 2026 17:19:35 +0100 Subject: [PATCH 071/168] fix: merge conflicts --- tests/Unit/Routes/StoreRoutesTest.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/Unit/Routes/StoreRoutesTest.php b/tests/Unit/Routes/StoreRoutesTest.php index 12297bd59..b7085a7d3 100644 --- a/tests/Unit/Routes/StoreRoutesTest.php +++ b/tests/Unit/Routes/StoreRoutesTest.php @@ -2,7 +2,6 @@ namespace Tests\Unit\Routes; -use App\StateToken; use Auth; use App\Centre; use App\CentreUser; @@ -105,7 +104,7 @@ public function setUp(): void * * @return void */ - public function testLoginGuestRoute(): void + public function testLogoutGuestRoute(): void { $this->actingAs($this->centreUser, 'store') ->post(URL::route('store.logout')) @@ -133,11 +132,11 @@ public function testForgotPasswordGuestRoute(): void ->assertResponseStatus(200); } - public function testLogoutRouteGate(): void + public function testLoginRoute(): void { Auth::logout(); - $this->visit($this->logoutRoute) - ->seePageIs(URL::route('store.logoin')) + $this->visit(route('store.login')) + ->seePageIs(URL::route('store.login')) ->assertResponseStatus(200); } From 4a3697e4634527e4bea84673e44cfbc183fa7b2b Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 9 Apr 2026 21:00:36 +0100 Subject: [PATCH 072/168] fix: fix tests --- .../Controllers/Service/Admin/CentreControllerTest.php | 7 ++----- tests/Unit/Routes/ServiceRoutesTest.php | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/Unit/Controllers/Service/Admin/CentreControllerTest.php b/tests/Unit/Controllers/Service/Admin/CentreControllerTest.php index c23bab60a..710ee4ddf 100644 --- a/tests/Unit/Controllers/Service/Admin/CentreControllerTest.php +++ b/tests/Unit/Controllers/Service/Admin/CentreControllerTest.php @@ -71,7 +71,7 @@ public function testICanSeeAnEditButtonOnTheListOfCentres(): void ; } - public function testICanUpdateACentreName(): void + public function testICanUpdateACentre(): void { $centre = factory(Centre::class)->create(); $this->seeInDatabase('centres', [ @@ -79,10 +79,7 @@ public function testICanUpdateACentreName(): void 'name' => $centre->name ]); - $data = [ - 'id' => $centre->id, - 'name' => 'New Centre Name', - ]; + $data = array_merge($centre->getAttributes(), ['name' => 'New Centre Name']); $this->actingAs($this->adminUser, 'admin') ->put( route('admin.centres.update', ['centre' => $centre->id]), diff --git a/tests/Unit/Routes/ServiceRoutesTest.php b/tests/Unit/Routes/ServiceRoutesTest.php index ac1f2da70..067d43213 100644 --- a/tests/Unit/Routes/ServiceRoutesTest.php +++ b/tests/Unit/Routes/ServiceRoutesTest.php @@ -33,7 +33,7 @@ class ServiceRoutesTest extends StoreTestCase 'admin.deliveries.index' => [], 'admin.centres.index' => [], 'admin.centres.create' => [], - 'admin.centre_neighbours.index' => ['id' => 1], + 'admin.centre_neighbours.index' => ['centre' => 1], 'admin.sponsors.index' => [], 'admin.sponsors.create' => [], 'admin.markets.index' => [], @@ -113,6 +113,7 @@ public function testRouteGates(): void ->makeRequest($method, route($route, $params)) ->followRedirects() ->response; + // Expecting 403 or return to "/login" $this->assertTrue( $response->isForbidden() From 20f71229d06c3707a4b2111a670de8986ef213c1 Mon Sep 17 00:00:00 2001 From: charles strange Date: Fri, 10 Apr 2026 09:52:38 +0100 Subject: [PATCH 073/168] feature: add migration to markets to link to a centre --- ...4810_add_centre_id_to_internal_markets.php | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 database/migrations/2026_04_10_094810_add_centre_id_to_internal_markets.php diff --git a/database/migrations/2026_04_10_094810_add_centre_id_to_internal_markets.php b/database/migrations/2026_04_10_094810_add_centre_id_to_internal_markets.php new file mode 100644 index 000000000..f41a0688e --- /dev/null +++ b/database/migrations/2026_04_10_094810_add_centre_id_to_internal_markets.php @@ -0,0 +1,31 @@ +unsignedInteger('centre_id')->nullable()->after('sponsor_id'); + $table->foreign('centre_id')->references('id')->on('centres')->nullOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::withoutForeignKeyConstraints(static function () { + Schema::table('markets', static function (Blueprint $table) { + $table->dropForeign(['centre_id']); + $table->dropColumn('centre_id'); + }); + }); + } +}; From 589406b5f40c9e697229fa0c8a37525191a934f3 Mon Sep 17 00:00:00 2001 From: charles strange Date: Fri, 10 Apr 2026 10:48:43 +0100 Subject: [PATCH 074/168] feature: update models, reationships and scopes --- app/Centre.php | 17 +++- app/Market.php | 46 +++++++---- tests/Unit/Models/CentreModelTest.php | 28 +++---- tests/Unit/Models/MarketModelTest.php | 107 +++++++++++++++++++++++--- 4 files changed, 160 insertions(+), 38 deletions(-) diff --git a/app/Centre.php b/app/Centre.php index a678e6d4e..6cb74520c 100644 --- a/app/Centre.php +++ b/app/Centre.php @@ -28,7 +28,11 @@ class Centre extends Model * @var array */ protected $fillable = [ - 'name', 'prefix', 'print_pref', 'sponsor_id', 'can_collect' + 'name', + 'prefix', + 'print_pref', + 'sponsor_id', + 'can_collect', ]; /** @@ -45,7 +49,7 @@ class Centre extends Model * @var array */ protected $casts = [ - 'can_collect' => 'boolean' + 'can_collect' => 'boolean', ]; public function nextCentreSequence(): int @@ -64,6 +68,15 @@ public function nextCentreSequence(): int return $sequence; } + /** + * All internal markets for this centre. + * Use the open() scope to restrict to those with at least one trader. + */ + public function markets(): HasMany + { + return $this->hasMany(Market::class); + } + /** * Get the Registrations for this Centre */ diff --git a/app/Market.php b/app/Market.php index 8cd9c1e77..90d417172 100644 --- a/app/Market.php +++ b/app/Market.php @@ -3,6 +3,7 @@ namespace App; use DateTimeInterface; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -28,6 +29,7 @@ class Market extends Model 'name', 'location', 'sponsor_id', + 'centre_id', 'payment_message', ]; @@ -50,44 +52,60 @@ class Market extends Model 'sponsor_shortcode', ]; + /** + * If this is an "internal market" it will have a centre + */ + public function centre(): BelongsTo + { + return $this->belongsTo(Centre::class); + } + + /** * Get the sponsor this market belongs to. - * - * @return BelongsTo */ - public function sponsor() + public function sponsor(): BelongsTo { return $this->belongsTo(Sponsor::class); } /** * Get the traders this market has. - * - * @return HasMany */ - public function traders() + public function traders(): HasMany { return $this->hasMany(Trader::class); } + /** + * Scope to markets that have at least one trader — i.e. are open for business. + */ + public function scopeTrading(Builder $query): Builder + { + return $query->has('traders'); + } + /** * Get the sponsor shortcode. - * - * @return string */ - public function getSponsorShortcodeAttribute() + public function getSponsorShortcodeAttribute(): ?string { - return $this->sponsor->shortcode; + return $this->sponsor?->shortcode; } /** * Prepare a date for array / JSON serialization. - * - * @param \DateTimeInterface $date - * @return string */ - protected function serializeDate(DateTimeInterface $date) + protected function serializeDate(DateTimeInterface $date): string { return $date->format('Y-m-d H:i:s'); } + + /** + * Is it internal? + */ + public function isInternal(): bool + { + return $this->centre_id !== null; + } } diff --git a/tests/Unit/Models/CentreModelTest.php b/tests/Unit/Models/CentreModelTest.php index d55097a8a..6c0705b66 100644 --- a/tests/Unit/Models/CentreModelTest.php +++ b/tests/Unit/Models/CentreModelTest.php @@ -3,9 +3,10 @@ namespace Tests\Unit\Models; use App\Centre; +use App\CentreUser; use App\Registration; use App\Sponsor; -use App\CentreUser; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Collection; use Tests\TestCase; @@ -14,16 +15,15 @@ class CentreModelTest extends TestCase { use RefreshDatabase; - public function testItHasExpectedAttributes(): void { $centre = factory(Centre::class)->make(); $this->assertNotNull($centre->name); $this->assertNotNull($centre->sponsor_id); $this->assertContains($centre->print_pref, config('arc.print_preferences')); + $this->assertFalse($centre->can_collect); } - public function testItHasASponsor(): void { $centre = factory(Centre::class)->create([ @@ -32,7 +32,6 @@ public function testItHasASponsor(): void $this->assertInstanceOf(Sponsor::class, $centre->sponsor); } - public function testItCanHaveRegistrations(): void { $centre = factory(Centre::class)->create(); @@ -44,7 +43,6 @@ public function testItCanHaveRegistrations(): void $this->assertInstanceOf(Registration::class, $registrations[0]); } - public function testItCanHaveNoRegistrations(): void { $centre = factory(Centre::class)->create(); @@ -53,7 +51,6 @@ public function testItCanHaveNoRegistrations(): void $this->assertEquals(0, $registrations->count()); } - public function testItCanHaveUsers(): void { $centre = factory(Centre::class)->create(); @@ -73,7 +70,6 @@ function (CentreUser $centreUser) use ($centre) { $this->assertInstanceOf(CentreUser::class, $centreUsers[0]); } - public function testItCanHaveNeighbours(): void { $sponsor_a = factory(Sponsor::class)->create(); @@ -100,12 +96,6 @@ public function testItCanHaveNeighbours(): void } } - public function testItCanNotCollectByDefault(): void - { - $centre = factory(Centre::class)->create(); - $this->assertFalse($centre->can_collect); - } - public function testItCanBeSetToCollect(): void { $centre = factory(Centre::class)->states('collecting')->create(); @@ -125,4 +115,16 @@ public function testCanCollectCanBeToggledToFalse(): void $centre->save(); $this->assertFalse(Centre::find($centre->id)->can_collect); } + + public function testMarketsIsAHasManyRelation(): void + { + $centre = factory(Centre::class)->create(); + $this->assertInstanceOf(HasMany::class, $centre->markets()); + } + + public function testMarketsOnNonCollectingCentreIsEmpty(): void + { + $centre = factory(Centre::class)->create(); + $this->assertCount(0, $centre->markets); + } } diff --git a/tests/Unit/Models/MarketModelTest.php b/tests/Unit/Models/MarketModelTest.php index 0592db20a..39c946181 100644 --- a/tests/Unit/Models/MarketModelTest.php +++ b/tests/Unit/Models/MarketModelTest.php @@ -2,10 +2,12 @@ namespace Tests\Unit\Models; +use App\Centre; use App\Market; use App\Sponsor; -use Illuminate\Database\Eloquent\Relations\HasMany; +use App\Trader; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -13,8 +15,9 @@ class MarketModelTest extends TestCase { use RefreshDatabase; - protected $market; - protected $sponsor; + protected Market $market; + protected Sponsor $sponsor; + protected function setUp(): void { parent::setUp(); @@ -25,8 +28,6 @@ protected function setUp(): void public function testMarketIsCreatedWithExpectedAttributes(): void { $m = $this->market; - // Keeping it simple to make writing test suite less onerous. - // The default error returned by asserts will be enough. $this->assertInstanceOf(Market::class, $m); $this->assertNotNull($m->name); $this->assertNotNull($m->location); @@ -38,7 +39,6 @@ public function testMarketIsCreatedWithExpectedAttributes(): void public function testMarketBelongsToSponsor(): void { - $this->assertInstanceOf(BelongsTo::class, $this->market->sponsor()); $this->assertInstanceOf(Sponsor::class, $this->market->sponsor); } @@ -49,9 +49,7 @@ public function testMarketCanHaveManyTraders(): void public function testGetSponsorShortcodeAttribute(): void { - $shortcode_market = $this->market->sponsor_shortcode; - $shortcode_sponsor = $this->sponsor->shortcode; - $this->assertEquals($shortcode_sponsor, $shortcode_market); + $this->assertEquals($this->sponsor->shortcode, $this->market->sponsor_shortcode); } public function testSoftDeleteMarket(): void @@ -60,4 +58,95 @@ public function testSoftDeleteMarket(): void $this->assertCount(1, Market::withTrashed()->get()); $this->assertCount(0, Market::all()); } + + public function testSponsorMarketIsNotInternal(): void + { + $this->assertFalse($this->market->isInternal()); + } + + public function testInternalMarketIsInternal(): void + { + $centre = factory(Centre::class)->create(); + $market = factory(Market::class)->create([ + 'centre_id' => $centre->id, + 'sponsor_id' => null, + ]); + + $this->assertTrue($market->isInternal()); + } + + public function testInternalMarketBelongsToCentre(): void + { + $centre = factory(Centre::class)->create(); + $market = factory(Market::class)->create([ + 'centre_id' => $centre->id, + 'sponsor_id' => null, + ]); + + $this->assertInstanceOf(BelongsTo::class, $market->centre()); + $this->assertInstanceOf(Centre::class, $market->centre); + $this->assertEquals($centre->id, $market->centre->id); + } + + public function testSponsorMarketHasNullCentre(): void + { + $this->assertNull($this->market->centre); + } + + public function testInternalMarketCanHaveMultipleTraders(): void + { + $centre = factory(Centre::class)->create(); + $market = factory(Market::class)->create([ + 'centre_id' => $centre->id, + ]); + + factory(Trader::class, 2)->create(['market_id' => $market->id]); + + $this->assertCount(2, $market->traders); + } + + public function testInternalMarketWithNoTradersHasEmptyTradersCollection(): void + { + $centre = factory(Centre::class)->create(); + $market = factory(Market::class)->create([ + 'centre_id' => $centre->id, + ]); + + $this->assertCount(0, $market->traders); + } + + public function testTradingScopeReturnsMarketsWithTraders(): void + { + $centre = factory(Centre::class)->create(); + $market = factory(Market::class)->create([ + 'centre_id' => $centre->id, + ]); + factory(Trader::class)->create(['market_id' => $market->id]); + + $this->assertCount(1, Market::trading()->where('centre_id', $centre->id)->get()); + } + + public function testTradingScopeExcludesMarketsWithNoTraders(): void + { + $centre = factory(Centre::class)->create(); + factory(Market::class)->create([ + 'centre_id' => $centre->id, + ]); + + $this->assertCount(0, Market::trading()->where('centre_id', $centre->id)->get()); + } + + public function testSoftDeletedInternalMarketIsRestorable(): void + { + $centre = factory(Centre::class)->create(); + $market = factory(Market::class)->create([ + 'centre_id' => $centre->id, + ]); + + $market->delete(); + $this->assertNull(Market::find($market->id)); + + $market->restore(); + $this->assertNotNull(Market::find($market->id)); + } } From cf7aaa0edaf1b625d04c9f5c61d066186039673e Mon Sep 17 00:00:00 2001 From: charles strange Date: Fri, 10 Apr 2026 11:10:31 +0100 Subject: [PATCH 075/168] feature: add an observer to incercept changes to the centre --- app/Centre.php | 4 ++ app/Observers/CentreObserver.php | 35 ++++++++++++++++ .../CentreCollectionMarketService.php | 41 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 app/Observers/CentreObserver.php create mode 100644 app/Services/CentreCollectionMarketService.php diff --git a/app/Centre.php b/app/Centre.php index 6cb74520c..bff08d304 100644 --- a/app/Centre.php +++ b/app/Centre.php @@ -2,7 +2,9 @@ namespace App; +use App\Observers\CentreObserver; use Eloquent; +use Illuminate\Database\Eloquent\Attributes\ObservedBy; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\belongsToMany; @@ -20,6 +22,8 @@ * @property Centre[] $neighbours * @property Family[] $families */ + +#[ObservedBy(CentreObserver::class)] class Centre extends Model { /** diff --git a/app/Observers/CentreObserver.php b/app/Observers/CentreObserver.php new file mode 100644 index 000000000..ea4e3a5ae --- /dev/null +++ b/app/Observers/CentreObserver.php @@ -0,0 +1,35 @@ +marketService = $marketService; + } + + public function created(Centre $centre): void + { + if ($centre->can_collect) { + $this->marketService->ensureMarket($centre); + } + } + + public function updating(Centre $centre): void + { + if (!$centre->isDirty('can_collect')) { + return; + } + + if ($centre->can_collect) { + $this->marketService->ensureMarket($centre); + } + } +} diff --git a/app/Services/CentreCollectionMarketService.php b/app/Services/CentreCollectionMarketService.php new file mode 100644 index 000000000..994121196 --- /dev/null +++ b/app/Services/CentreCollectionMarketService.php @@ -0,0 +1,41 @@ +where('centre_id', $centre->id) + ->first(); + + if ($market) { + // Restore if soft-deleted + if ($market->trashed()) { + $market->restore(); + } + } else { + $market = Market::create([ + 'name' => $centre->name . ' Collection', + 'location' => $centre->name, + 'centre_id' => $centre->id, + 'sponsor_id' => $centre->sponsor->id, + 'payment_message' => '', + ]); + + Trader::create([ + 'name' => $centre->name . ' Collection Trader', + 'market_id' => $market->id, + ]); + } + + return $market; + } +} From 0407f0a81d04c683bcb4277446875ac3b4d3256f Mon Sep 17 00:00:00 2001 From: charles strange Date: Fri, 10 Apr 2026 12:35:13 +0100 Subject: [PATCH 076/168] refactor: be more exact with the service and observer, add tests --- app/Observers/CentreObserver.php | 4 +- .../CentreCollectionMarketService.php | 47 ++++---- tests/Unit/Observers/CentreObserverTest.php | 85 +++++++++++++++ .../CentreCollectionMarketServiceTest.php | 100 ++++++++++++++++++ 4 files changed, 208 insertions(+), 28 deletions(-) create mode 100644 tests/Unit/Observers/CentreObserverTest.php create mode 100644 tests/Unit/Services/CentreCollectionMarketServiceTest.php diff --git a/app/Observers/CentreObserver.php b/app/Observers/CentreObserver.php index ea4e3a5ae..7d38058eb 100644 --- a/app/Observers/CentreObserver.php +++ b/app/Observers/CentreObserver.php @@ -18,7 +18,7 @@ public function __construct( public function created(Centre $centre): void { if ($centre->can_collect) { - $this->marketService->ensureMarket($centre); + $this->marketService->ensureTradingMarket($centre); } } @@ -29,7 +29,7 @@ public function updating(Centre $centre): void } if ($centre->can_collect) { - $this->marketService->ensureMarket($centre); + $this->marketService->ensureTradingMarket($centre); } } } diff --git a/app/Services/CentreCollectionMarketService.php b/app/Services/CentreCollectionMarketService.php index 994121196..2b838c604 100644 --- a/app/Services/CentreCollectionMarketService.php +++ b/app/Services/CentreCollectionMarketService.php @@ -8,34 +8,29 @@ class CentreCollectionMarketService { - public function ensureMarket(Centre $centre): Market + public function ensureTradingMarket(Centre $centre): void { - // withTrashed guards against the edge case where someone - // manually soft-deleted the market outside normal flows. - $market = Market::withTrashed() - ->where('centre_id', $centre->id) - ->first(); - - if ($market) { - // Restore if soft-deleted - if ($market->trashed()) { - $market->restore(); - } - } else { - $market = Market::create([ - 'name' => $centre->name . ' Collection', - 'location' => $centre->name, - 'centre_id' => $centre->id, - 'sponsor_id' => $centre->sponsor->id, - 'payment_message' => '', - ]); - - Trader::create([ - 'name' => $centre->name . ' Collection Trader', - 'market_id' => $market->id, - ]); + // is there already one from a prior can_collect toggle? + if (Market::where('centre_id', $centre->id)->trading()->exists()) { + return; } - return $market; + // there are no active markets with traders that are assigned to this centre, + // better make one ... + $market = Market::create([ + 'name' => $centre->name . ' (Internal)', + 'location' => $centre->name, + 'centre_id' => $centre->id, + // assign it the centre's sponsor/area + 'sponsor_id' => $centre->sponsor->id, + // don't need one of these, the payment recommendation will be automated + 'payment_message' => '', + ]); + + // ... and it's trader + Trader::create([ + 'name' => $centre->name . ' (Internal)', + 'market_id' => $market->id, + ]); } } diff --git a/tests/Unit/Observers/CentreObserverTest.php b/tests/Unit/Observers/CentreObserverTest.php new file mode 100644 index 000000000..625157cbe --- /dev/null +++ b/tests/Unit/Observers/CentreObserverTest.php @@ -0,0 +1,85 @@ +marketService = Mockery::mock(CentreCollectionMarketService::class); + $this->observer = new CentreObserver($this->marketService); + } + + public function testCreatedCallsEnsureTradingMarketWhenCentreCanCollect(): void + { + $centre = factory(Centre::class)->make(['can_collect' => true]); + + $this->marketService + ->shouldReceive('ensureTradingMarket') + ->once() + ->with($centre); + + $this->observer->created($centre); + } + + public function testCreatedDoesNotCallEnsureTradingMarketWhenCentreCannotCollect(): void + { + $centre = factory(Centre::class)->make(['can_collect' => false]); + + $this->marketService + ->shouldReceive('ensureTradingMarket') + ->never(); + + $this->observer->created($centre); + } + + public function testUpdatingCallsEnsureTradingMarketWhenCanCollectFlipsToTrue(): void + { + $centre = factory(Centre::class)->create(['can_collect' => false]); + $centre->can_collect = true; + + $this->marketService + ->shouldReceive('ensureTradingMarket') + ->once() + ->with($centre); + + $this->observer->updating($centre); + } + + public function testUpdatingDoesNotCallEnsureTradingMarketWhenCanCollectFlipsToFalse(): void + { + $centre = factory(Centre::class)->create(['can_collect' => true]); + $centre->can_collect = false; + + $this->marketService + ->shouldReceive('ensureTradingMarket') + ->never(); + + $this->observer->updating($centre); + } + + public function testUpdatingDoesNotCallEnsureTradingMarketWhenCanCollectIsUnchanged(): void + { + $centre = factory(Centre::class)->create(['can_collect' => true]); + $centre->name = 'A different name'; + + $this->marketService + ->shouldReceive('ensureTradingMarket') + ->never(); + + $this->observer->updating($centre); + } +} diff --git a/tests/Unit/Services/CentreCollectionMarketServiceTest.php b/tests/Unit/Services/CentreCollectionMarketServiceTest.php new file mode 100644 index 000000000..5009f3243 --- /dev/null +++ b/tests/Unit/Services/CentreCollectionMarketServiceTest.php @@ -0,0 +1,100 @@ +service = new CentreCollectionMarketService(); + $this->centre = factory(Centre::class)->create(['can_collect' => false]); + } + + public function testItCreatesAMarketWhenNoneExists(): void + { + $this->service->ensureTradingMarket($this->centre); + + $this->assertCount(1, Market::where('centre_id', $this->centre->id)->get()); + } + + public function testItCreatesATraderOnTheNewMarket(): void + { + $this->service->ensureTradingMarket($this->centre); + + $market = Market::where('centre_id', $this->centre->id)->first(); + $this->assertCount(1, $market->traders); + } + + public function testNewMarketHasExpectedAttributes(): void + { + $this->service->ensureTradingMarket($this->centre); + + $market = Market::where('centre_id', $this->centre->id)->first(); + $this->assertSame($this->centre->name . ' (Internal)', $market->name); + $this->assertSame($this->centre->name, $market->location); + $this->assertSame($this->centre->id, $market->centre_id); + $this->assertSame($this->centre->sponsor->id, $market->sponsor_id); + $this->assertSame('', $market->payment_message); + } + + public function testNewTraderHasExpectedAttributes(): void + { + $this->service->ensureTradingMarket($this->centre); + + $market = Market::where('centre_id', $this->centre->id)->first(); + $trader = $market->traders()->first(); + $this->assertSame($this->centre->name . ' (Internal)', $trader->name); + $this->assertSame($market->id, $trader->market_id); + } + + public function testItDoesNotCreateAMarketWhenOneTradingMarketExists(): void + { + $market = factory(Market::class)->create(['centre_id' => $this->centre->id]); + factory(Trader::class)->create(['market_id' => $market->id]); + + $this->service->ensureTradingMarket($this->centre); + + $this->assertCount(1, Market::where('centre_id', $this->centre->id)->get()); + } + + public function testItDoesNotCreateAMarketWhenMultipleTradingMarketsExist(): void + { + factory(Market::class, 2)->create(['centre_id' => $this->centre->id]) + ->each(function ($market) { + return factory(Trader::class)->create(['market_id' => $market->id]); + }); + + $this->service->ensureTradingMarket($this->centre); + + $this->assertCount(2, Market::where('centre_id', $this->centre->id)->get()); + } + + public function testItCreatesAMarketWhenOnlyNonTradingMarketsExist(): void + { + factory(Market::class)->create(['centre_id' => $this->centre->id]); + + $this->service->ensureTradingMarket($this->centre); + + $this->assertCount(2, Market::where('centre_id', $this->centre->id)->get()); + } + + public function testItReturnsVoid(): void + { + $result = $this->service->ensureTradingMarket($this->centre); + + $this->assertNull($result); + } +} From 5a1e89cdd833f4249ae9f88a2dd7aa9ae164031b Mon Sep 17 00:00:00 2001 From: charles strange Date: Fri, 10 Apr 2026 15:10:49 +0100 Subject: [PATCH 077/168] fix: wrong id on the save button --- resources/views/service/centres/edit.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/service/centres/edit.blade.php b/resources/views/service/centres/edit.blade.php index 756264e93..81b7a977c 100644 --- a/resources/views/service/centres/edit.blade.php +++ b/resources/views/service/centres/edit.blade.php @@ -80,7 +80,7 @@ class="styled-checkbox @error('can_collect') error @enderror" @include('service.partials.validationMessages', ['inputName' => 'can_collect']) - + From 9900c58b8675d85cde419ebcac8232306c123b38 Mon Sep 17 00:00:00 2001 From: charlesstrange2 <25037036+charlesstrange2@users.noreply.github.com> Date: Fri, 10 Apr 2026 15:50:37 +0100 Subject: [PATCH 078/168] Update app/Services/CentreCollectionMarketService.php Co-authored-by: Colin Stewart --- app/Services/CentreCollectionMarketService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/CentreCollectionMarketService.php b/app/Services/CentreCollectionMarketService.php index 2b838c604..bfeb7fd88 100644 --- a/app/Services/CentreCollectionMarketService.php +++ b/app/Services/CentreCollectionMarketService.php @@ -27,7 +27,7 @@ public function ensureTradingMarket(Centre $centre): void 'payment_message' => '', ]); - // ... and it's trader + // ... and its trader Trader::create([ 'name' => $centre->name . ' (Internal)', 'market_id' => $market->id, From fa2d4374a35ba1d390e2ab602cdab6002b23f946 Mon Sep 17 00:00:00 2001 From: charles strange Date: Fri, 10 Apr 2026 15:56:36 +0100 Subject: [PATCH 079/168] feature: add "Can Collect" (yes/no) to the centres view. --- resources/views/service/centres/index.blade.php | 12 ++++++++---- resources/views/service/centreusers/index.blade.php | 10 ++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/resources/views/service/centres/index.blade.php b/resources/views/service/centres/index.blade.php index 00c1460cd..0585ae5fb 100644 --- a/resources/views/service/centres/index.blade.php +++ b/resources/views/service/centres/index.blade.php @@ -18,8 +18,9 @@ Name RVID Prefix Area + Can Collect Form - Edit + @@ -28,6 +29,7 @@ {{ $centre->name }} {{ $centre->prefix }} {{ $centre->sponsor->name }} + {{ $centre->can_collect ? 'Yes' : 'No' }} {{ ucwords($centre->print_pref) }} @@ -40,11 +42,13 @@ - - + + @endsection diff --git a/resources/views/service/centreusers/index.blade.php b/resources/views/service/centreusers/index.blade.php index c895b02d2..ea86dd746 100644 --- a/resources/views/service/centreusers/index.blade.php +++ b/resources/views/service/centreusers/index.blade.php @@ -21,7 +21,7 @@ Home Centre Alternative Centres Downloader - Edit + @@ -60,11 +60,13 @@ - - + + @endsection From fd809a62391a4dd5f9b3e52bc549dbcc4fd7688e Mon Sep 17 00:00:00 2001 From: charles strange Date: Fri, 10 Apr 2026 16:00:11 +0100 Subject: [PATCH 080/168] fix: update datables version --- resources/views/service/centres/index.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/service/centres/index.blade.php b/resources/views/service/centres/index.blade.php index 0585ae5fb..2b8c7d954 100644 --- a/resources/views/service/centres/index.blade.php +++ b/resources/views/service/centres/index.blade.php @@ -42,8 +42,8 @@ - - + + -@endsection +@endpush diff --git a/resources/views/store/partials/voucher-manager/allocate-vouchers.blade.php b/resources/views/store/partials/voucher-manager/allocate-vouchers.blade.php new file mode 100644 index 000000000..47227daa4 --- /dev/null +++ b/resources/views/store/partials/voucher-manager/allocate-vouchers.blade.php @@ -0,0 +1,147 @@ +{{-- Requires: $registration, $vouchers, $vouchers_amount, $errors --}} +
+
+ +

Allocate Vouchers

+
+ + {{-- Range entry --}} +
+ @csrf +
+ + + +
+
+ +

OR

+ + {{-- Single entry --}} +
+ @csrf +
+ + +
+
+ + @includeWhen($errors->count() > 0, 'store.partials.errors', ['error_array' => $errors->all()]) + @includeWhen(Session::get('error_messages'), 'store.partials.errors', ['error_array' => Session::get('error_messages')]) + + + +
+ Vouchers added + {{ $vouchers_amount }} + +
$vouchers_amount === 0])> +
+ @method('DELETE') + @csrf + +
+
+ +
$vouchers_amount === 0])> +
+ @method('DELETE') + @csrf + + + + + + @foreach($vouchers as $voucher) + + + + + @endforeach +
Voucher codeRemove
{{ $voucher->code }} + +
+
+
+
+
+ +@pushonce('scripts') + +@endpushonce diff --git a/resources/views/store/partials/voucher-manager/collection-history.blade.php b/resources/views/store/partials/voucher-manager/collection-history.blade.php new file mode 100644 index 000000000..94137ef74 --- /dev/null +++ b/resources/views/store/partials/voucher-manager/collection-history.blade.php @@ -0,0 +1,33 @@ +{{-- Requires: $programme, $registration, $entitlement, $lastCollection --}} +
+
+ +

Collection History

+
+ +
+
+

This {{ $programme === 0 ? 'family' : 'household' }} should collect:

+

{{ $entitlement }} vouchers per week

+
+ +
+ @isset($lastCollection) +

Their last collection was:

+

{{ $lastCollection }}

+ @else +

+ This {{ $programme === 0 ? 'family' : 'household' }} has not collected +

+ @endisset +
+
+ + + Full Collection History + +
diff --git a/resources/views/store/partials/voucher-manager/family-info.blade.php b/resources/views/store/partials/voucher-manager/family-info.blade.php new file mode 100644 index 000000000..7a1b586b7 --- /dev/null +++ b/resources/views/store/partials/voucher-manager/family-info.blade.php @@ -0,0 +1,46 @@ +{{-- Requires: $programme, $registration, $pri_carer, $children, $noticeReasons --}} +
+
+ +

{{ $programme === 0 ? 'This Family' : 'Voucher Collectors' }}

+
+ +
+

Their RV-ID is: {{ $registration->family->rvid }}

+
+ +
+
+

Main {{ $programme === 0 ? 'Carer' : 'Participant' }}

+

{{ $pri_carer->name }}

+
+
+

{{ $programme === 0 ? 'Children:' : 'Household' }}

+
    + @foreach($children as $child) +
  • {{ $child->getAgeString() }}
  • + @endforeach +
+
+
+ + @if($programme === 0) + @includeWhen(!empty($noticeReasons), 'store.partials.notice_box', ['noticeReasons' => $noticeReasons]) + @endif + + + Go to edit {{ $programme === 0 ? 'family' : 'household' }} + + + + Find another {{ $programme === 0 ? 'family' : 'household' }} + +
diff --git a/resources/views/store/partials/voucher-manager/pickup.blade.php b/resources/views/store/partials/voucher-manager/pickup.blade.php new file mode 100644 index 000000000..193518faf --- /dev/null +++ b/resources/views/store/partials/voucher-manager/pickup.blade.php @@ -0,0 +1,93 @@ +{{-- Requires: $programme, $registration, $vouchers_amount, $carers, $centre --}} +
+
+ +

Voucher Pick Up

+
+ +
+

There are {{ $vouchers_amount }} vouchers waiting for + this {{ $programme === 0 ? 'family' : 'household' }}

+
+ +
+ @method('PUT') + @csrf +
+
+ +
+ + +
+
+ +
+ +
+ + + +
+
+ +
+ +
+ + +
+
+ + +
+
+ + + Change allocated vouchers + +
+ +@pushonce('scripts') + +@endpushonce From fdf0b088b27104f9d0405d5fc7b18ccbf95259f0 Mon Sep 17 00:00:00 2001 From: charles strange Date: Mon, 13 Apr 2026 16:10:20 +0100 Subject: [PATCH 087/168] fix: ccuser needs an nt email for password resets --- database/seeders/CentreUsersSeeder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/seeders/CentreUsersSeeder.php b/database/seeders/CentreUsersSeeder.php index f6ba220da..2708623e0 100644 --- a/database/seeders/CentreUsersSeeder.php +++ b/database/seeders/CentreUsersSeeder.php @@ -31,7 +31,7 @@ public function run(): void private function seedCcUser(): void { $user = $this->createAndAttach( - attributes: ['name' => 'ARC CC User', 'email' => 'arc+ccuser@exmaple.com', 'role' => 'centre_user'], + attributes: ['name' => 'ARC CC User', 'email' => 'arc+ccuser@neontribe.co.uk', 'role' => 'centre_user'], centreId: 1, isHome: true ); From 01ca3e68c31814fc2477f25090b4f76712e8c06d Mon Sep 17 00:00:00 2001 From: charles strange Date: Mon, 13 Apr 2026 16:37:15 +0100 Subject: [PATCH 088/168] refactor: organise the partials better into direcotries --- .../views/store/create_registration.blade.php | 12 +-- .../views/store/edit_registration.blade.php | 52 ++++++------ .../views/store/manage_vouchers.blade.php | 80 +------------------ .../views/store/partials/errors.blade.php | 2 +- .../add_child_form.blade.php | 8 +- .../add_participant_form.blade.php | 8 +- .../ageInput.blade.php | 0 .../dobInput.blade.php | 0 .../family.blade.php | 20 ++--- .../householdSP.blade.php | 12 +-- .../notice_box.blade.php | 0 .../other_info.blade.php | 2 +- .../other_infoSP.blade.php | 0 .../pri-carer_fields.blade.php | 2 +- .../sec-carers_fields.blade.php | 2 +- .../voucher_collectors.blade.php | 4 +- .../voucher_collectorsSP.blade.php | 6 +- .../allocate-vouchers.blade.php | 0 .../collection-history.blade.php | 0 .../voucher-manager/family-info.blade.php | 2 +- .../voucher-manager/pickup.blade.php | 0 21 files changed, 72 insertions(+), 140 deletions(-) rename resources/views/store/{partials => registrations}/add_child_form.blade.php (81%) rename resources/views/store/{partials => registrations}/add_participant_form.blade.php (76%) rename resources/views/store/{partials => registrations}/ageInput.blade.php (100%) rename resources/views/store/{partials => registrations}/dobInput.blade.php (100%) rename resources/views/store/{partials => registrations}/family.blade.php (88%) rename resources/views/store/{partials => registrations}/householdSP.blade.php (91%) rename resources/views/store/{partials => registrations}/notice_box.blade.php (100%) rename resources/views/store/{partials => registrations}/other_info.blade.php (98%) rename resources/views/store/{partials => registrations}/other_infoSP.blade.php (100%) rename resources/views/store/{partials => registrations}/pri-carer_fields.blade.php (98%) rename resources/views/store/{partials => registrations}/sec-carers_fields.blade.php (98%) rename resources/views/store/{partials => registrations}/voucher_collectors.blade.php (85%) rename resources/views/store/{partials => registrations}/voucher_collectorsSP.blade.php (88%) rename resources/views/store/{partials => }/voucher-manager/allocate-vouchers.blade.php (100%) rename resources/views/store/{partials => }/voucher-manager/collection-history.blade.php (100%) rename resources/views/store/{partials => }/voucher-manager/family-info.blade.php (92%) rename resources/views/store/{partials => }/voucher-manager/pickup.blade.php (100%) diff --git a/resources/views/store/create_registration.blade.php b/resources/views/store/create_registration.blade.php index 239cfb8b8..1668c4a51 100644 --- a/resources/views/store/create_registration.blade.php +++ b/resources/views/store/create_registration.blade.php @@ -14,18 +14,18 @@
{!! csrf_field() !!} - @include('store.partials.voucher_collectorsSP') - @include('store.partials.householdSP') - @include('store.partials.other_infoSP') + @include('store.registrations.voucher_collectorsSP') + @include('store.registrations.householdSP') + @include('store.registrations.other_infoSP')
@else
{!! csrf_field() !!} - @include('store.partials.voucher_collectors') - @include('store.partials.family') - @include('store.partials.other_info') + @include('store.registrations.voucher_collectors') + @include('store.registrations.family') + @include('store.registrations.other_info')
@endif diff --git a/resources/views/store/edit_registration.blade.php b/resources/views/store/edit_registration.blade.php index 36f2a8748..3684ab351 100644 --- a/resources/views/store/edit_registration.blade.php +++ b/resources/views/store/edit_registration.blade.php @@ -4,33 +4,35 @@ @section('content') -@include('store.partials.navbar', ['headerTitle' => 'Check or update']) + @include('store.partials.navbar', ['headerTitle' => 'Check or update']) -@if ($programme !== 0) -
-
$registration]) }}" method="post" class="full-height"> - {{ method_field('PUT') }} - {!! csrf_field() !!} + @if ($programme !== 0) +
+ $registration]) }}" method="post" + class="full-height"> + {{ method_field('PUT') }} + {!! csrf_field() !!} - @include('store.partials.voucher_collectorsSP') - @include('store.partials.householdSP') - @include('store.partials.other_infoSP') - -
-@else -
-
$registration]) }}" method="post" class="full-height"> - {{ method_field('PUT') }} - {!! csrf_field() !!} - - @include('store.partials.voucher_collectors') - @include('store.partials.family') - @include('store.partials.other_info') -
-
-@endif + @include('store.registrations.voucher_collectorsSP') + @include('store.registrations.householdSP') + @include('store.registrations.other_infoSP') + +
+ @else +
+
$registration]) }}" method="post" + class="full-height"> + {{ method_field('PUT') }} + {!! csrf_field() !!} + + @include('store.registrations.voucher_collectors') + @include('store.registrations.family') + @include('store.registrations.other_info') +
+
+ @endif - -@stack("bottom") + + @stack("bottom") @endsection diff --git a/resources/views/store/manage_vouchers.blade.php b/resources/views/store/manage_vouchers.blade.php index ace6364fc..9b4e97c65 100644 --- a/resources/views/store/manage_vouchers.blade.php +++ b/resources/views/store/manage_vouchers.blade.php @@ -7,83 +7,11 @@
- @include('store.partials.voucher-manager.family-info') - @include('store.partials.voucher-manager.collection-history') - @include('store.partials.voucher-manager.allocate-vouchers') - @include('store.partials.voucher-manager.pickup') + @include('store.voucher-manager.family-info') + @include('store.voucher-manager.collection-history') + @include('store.voucher-manager.allocate-vouchers') + @include('store.voucher-manager.pickup')
@endsection -@push('scripts') - -@endpush diff --git a/resources/views/store/partials/errors.blade.php b/resources/views/store/partials/errors.blade.php index 26161b6ea..138b710ba 100644 --- a/resources/views/store/partials/errors.blade.php +++ b/resources/views/store/partials/errors.blade.php @@ -7,4 +7,4 @@

{{ $error_text }}

@endforeach - \ No newline at end of file + diff --git a/resources/views/store/partials/add_child_form.blade.php b/resources/views/store/registrations/add_child_form.blade.php similarity index 81% rename from resources/views/store/partials/add_child_form.blade.php rename to resources/views/store/registrations/add_child_form.blade.php index 9c4cc75ea..e22360795 100644 --- a/resources/views/store/partials/add_child_form.blade.php +++ b/resources/views/store/registrations/add_child_form.blade.php @@ -3,8 +3,8 @@
- @include('store.partials.dobInput') -


+ @include('store.registrations.dobInput') +


- @include('store.partials.ageInput') + @include('store.registrations.ageInput')
-

+

diff --git a/resources/views/store/partials/ageInput.blade.php b/resources/views/store/registrations/ageInput.blade.php similarity index 100% rename from resources/views/store/partials/ageInput.blade.php rename to resources/views/store/registrations/ageInput.blade.php diff --git a/resources/views/store/partials/dobInput.blade.php b/resources/views/store/registrations/dobInput.blade.php similarity index 100% rename from resources/views/store/partials/dobInput.blade.php rename to resources/views/store/registrations/dobInput.blade.php diff --git a/resources/views/store/partials/family.blade.php b/resources/views/store/registrations/family.blade.php similarity index 88% rename from resources/views/store/partials/family.blade.php rename to resources/views/store/registrations/family.blade.php index e47d18105..a629e8845 100644 --- a/resources/views/store/partials/family.blade.php +++ b/resources/views/store/registrations/family.blade.php @@ -12,7 +12,7 @@ numbers, e.g. '06 2017' for June 2017.

- @include('store.partials.add_child_form', ['sponsorsRequiresID' => $sponsorsRequiresID]) + @include('store.registrations.add_child_form', ['sponsorsRequiresID' => $sponsorsRequiresID])
@@ -49,15 +49,15 @@ @endif @if ( !empty($deferrable) ) @endif
- deferred ? "checked" : null }} value="1" - > - + deferred ? "checked" : null }} value="1" + > + diff --git a/resources/views/store/partials/householdSP.blade.php b/resources/views/store/registrations/householdSP.blade.php similarity index 91% rename from resources/views/store/partials/householdSP.blade.php rename to resources/views/store/registrations/householdSP.blade.php index ae447a439..d8279013a 100644 --- a/resources/views/store/partials/householdSP.blade.php +++ b/resources/views/store/registrations/householdSP.blade.php @@ -10,7 +10,7 @@

To add a household member, complete the box below with their age.

- @include('store.partials.add_participant_form', ['sponsorsRequiresID' => $sponsorsRequiresID]) + @include('store.registrations.add_participant_form', ['sponsorsRequiresID' => $sponsorsRequiresID])
@@ -28,9 +28,10 @@ - +
{{ explode(',', $child->getAgeString())[0] }} is_pri_carer }} - >is_pri_carer }} + > diff --git a/resources/views/store/partials/notice_box.blade.php b/resources/views/store/registrations/notice_box.blade.php similarity index 100% rename from resources/views/store/partials/notice_box.blade.php rename to resources/views/store/registrations/notice_box.blade.php diff --git a/resources/views/store/partials/other_info.blade.php b/resources/views/store/registrations/other_info.blade.php similarity index 98% rename from resources/views/store/partials/other_info.blade.php rename to resources/views/store/registrations/other_info.blade.php index f60aa03e7..4c9ebceed 100644 --- a/resources/views/store/partials/other_info.blade.php +++ b/resources/views/store/registrations/other_info.blade.php @@ -95,7 +95,7 @@ {{-- This section should only exist in `edit` rather than `add new` --}} @if (isset($noticeReasons)) - @includeWhen(!empty($noticeReasons), 'store.partials.notice_box', ['noticeReasons' => $noticeReasons]) + @includeWhen(!empty($noticeReasons), 'store.registrations.notice_box', ['noticeReasons' => $noticeReasons]) @endif {{-- This section should only exist in `add new` rather than existing records --}} diff --git a/resources/views/store/partials/other_infoSP.blade.php b/resources/views/store/registrations/other_infoSP.blade.php similarity index 100% rename from resources/views/store/partials/other_infoSP.blade.php rename to resources/views/store/registrations/other_infoSP.blade.php diff --git a/resources/views/store/partials/pri-carer_fields.blade.php b/resources/views/store/registrations/pri-carer_fields.blade.php similarity index 98% rename from resources/views/store/partials/pri-carer_fields.blade.php rename to resources/views/store/registrations/pri-carer_fields.blade.php index 78eaacc16..36491c2cb 100644 --- a/resources/views/store/partials/pri-carer_fields.blade.php +++ b/resources/views/store/registrations/pri-carer_fields.blade.php @@ -1,5 +1,5 @@ {{-- - partial: store.partials.primary-carer-fields + partial: store.registrations.primary-carer-fields ════════════════════════════════════════════════════════════════════════ Renders the primary carer / participant input group: • name (text, required) diff --git a/resources/views/store/partials/sec-carers_fields.blade.php b/resources/views/store/registrations/sec-carers_fields.blade.php similarity index 98% rename from resources/views/store/partials/sec-carers_fields.blade.php rename to resources/views/store/registrations/sec-carers_fields.blade.php index 2a779c7e6..29ce3cbd9 100644 --- a/resources/views/store/partials/sec-carers_fields.blade.php +++ b/resources/views/store/registrations/sec-carers_fields.blade.php @@ -1,5 +1,5 @@ {{-- - partial: store.partials.sec-carers_fields + partial: store.registrations.sec-carers_fields ════════════════════════════════════════════════════════════════════════ Renders the voucher-collector adder widget and the secondary carers table, including both the persisted ($sec_carers) and the old()-repopulated diff --git a/resources/views/store/partials/voucher_collectors.blade.php b/resources/views/store/registrations/voucher_collectors.blade.php similarity index 85% rename from resources/views/store/partials/voucher_collectors.blade.php rename to resources/views/store/registrations/voucher_collectors.blade.php index 81f557ef1..da0cb8baf 100644 --- a/resources/views/store/partials/voucher_collectors.blade.php +++ b/resources/views/store/registrations/voucher_collectors.blade.php @@ -4,14 +4,14 @@

Voucher collectors

- @include('store.partials.pri-carer_fields', [ + @include('store.registrations.pri-carer_fields', [ 'labelName' => "Main carer's full name", 'labelTelno' => "Main carer's telephone number", 'labelEmail' => "Main carer's email address", 'labelEthnicity' => "Main carer's ethnic background (optional)", 'labelLanguage' => "Carer's main language (optional)", ]) - @include('store.partials.sec-carers_fields', [ + @include('store.registrations.sec-carers_fields', [ 'newCarersErrorMessage' => 'Please check you have valid carer names', ]) diff --git a/resources/views/store/partials/voucher_collectorsSP.blade.php b/resources/views/store/registrations/voucher_collectorsSP.blade.php similarity index 88% rename from resources/views/store/partials/voucher_collectorsSP.blade.php rename to resources/views/store/registrations/voucher_collectorsSP.blade.php index 70bc4770a..6e393299c 100644 --- a/resources/views/store/partials/voucher_collectorsSP.blade.php +++ b/resources/views/store/registrations/voucher_collectorsSP.blade.php @@ -4,7 +4,7 @@

Voucher collectors

- @include('store.partials.pri-carer_fields', [ + @include('store.registrations.pri-carer_fields', [ 'labelName' => "Main Participant's full name", 'labelTelno' => "Main participant's telephone number", 'labelEmail' => "Main participant's email address", @@ -13,7 +13,7 @@ ])
- @include('store.partials.ageInput') + @include('store.registrations.ageInput')
- @include('store.partials.sec-carers_fields', [ + @include('store.registrations.sec-carers_fields', [ 'newCarersErrorMessage' => 'Please check you have valid collector names', ]) diff --git a/resources/views/store/partials/voucher-manager/allocate-vouchers.blade.php b/resources/views/store/voucher-manager/allocate-vouchers.blade.php similarity index 100% rename from resources/views/store/partials/voucher-manager/allocate-vouchers.blade.php rename to resources/views/store/voucher-manager/allocate-vouchers.blade.php diff --git a/resources/views/store/partials/voucher-manager/collection-history.blade.php b/resources/views/store/voucher-manager/collection-history.blade.php similarity index 100% rename from resources/views/store/partials/voucher-manager/collection-history.blade.php rename to resources/views/store/voucher-manager/collection-history.blade.php diff --git a/resources/views/store/partials/voucher-manager/family-info.blade.php b/resources/views/store/voucher-manager/family-info.blade.php similarity index 92% rename from resources/views/store/partials/voucher-manager/family-info.blade.php rename to resources/views/store/voucher-manager/family-info.blade.php index 7a1b586b7..0604865e9 100644 --- a/resources/views/store/partials/voucher-manager/family-info.blade.php +++ b/resources/views/store/voucher-manager/family-info.blade.php @@ -25,7 +25,7 @@ @if($programme === 0) - @includeWhen(!empty($noticeReasons), 'store.partials.notice_box', ['noticeReasons' => $noticeReasons]) + @includeWhen(!empty($noticeReasons), 'store.registrations.notice_box', ['noticeReasons' => $noticeReasons]) @endif Date: Tue, 14 Apr 2026 22:30:54 +0100 Subject: [PATCH 089/168] revert: don't let users log in to "all centres" --- app/Listeners/CentreUserAuthenticated.php | 13 ++-- config/arc.php | 11 --- .../views/store/partials/masthead.blade.php | 73 ++++++++++--------- .../Listeners/CentreUserAuthenticatedTest.php | 39 ++-------- 4 files changed, 49 insertions(+), 87 deletions(-) diff --git a/app/Listeners/CentreUserAuthenticated.php b/app/Listeners/CentreUserAuthenticated.php index f4cab287e..36ca2d682 100644 --- a/app/Listeners/CentreUserAuthenticated.php +++ b/app/Listeners/CentreUserAuthenticated.php @@ -2,9 +2,8 @@ namespace App\Listeners; -use App\CentreUser; -use Config; use Illuminate\Auth\Events\Authenticated; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Session; class CentreUserAuthenticated @@ -20,12 +19,10 @@ public function handle(Authenticated $event): void } // a fresh login won't have this key if (Session::missing('CentreUserCurrentCentreId')) { - // set the session to the centre default or all the centres they're allowed - $default = Config::get('arc.default_to_home_centre') - ? $event->user->homeCentre?->id - : 'all'; - - Session::put('CentreUserCurrentCentreId', $default); + // could be null, so for now we'll pick the first centre, or fallback to null. + $id = $event->user->homeCentre?->id ?? $event->user->centres()->first()?->id; + Log::warning("Centre User {$event->user->id} does not have a home centre"); + Session::put('CentreUserCurrentCentreId', $id); } } } diff --git a/config/arc.php b/config/arc.php index adf54165f..7df01dc50 100644 --- a/config/arc.php +++ b/config/arc.php @@ -136,15 +136,4 @@ 'WOTH' => 'White - Any other White background', 'NOBT' => 'Not answered', ], - - /* - |-------------------------------------------------------------------------- - | Default home centre for store dropdown - | - `true` for home centre or - | - `false` for "all" centres a user can see - |-------------------------------------------------------------------------- - */ - - 'default_to_home_centre' => env('ARC_DEFAULT_TO_HOME_CENTRE', false), - ]; diff --git a/resources/views/store/partials/masthead.blade.php b/resources/views/store/partials/masthead.blade.php index c3a33f5fe..59e2070f7 100644 --- a/resources/views/store/partials/masthead.blade.php +++ b/resources/views/store/partials/masthead.blade.php @@ -1,52 +1,55 @@
- @if (!Auth::guest()) + @auth
-
- -
-
- @endif + @endauth +
- @if (!Auth::guest()) + + @auth
    -
  • User: {{ Auth::user()->name }}
  • - @if ( config('app.env') !== 'production' ) +
  • User: {{ Auth::user()->name }}
  • + + @if (app()->environment() !== 'production')
  • Programme: {{ Auth::user()->centre->sponsor->programme_name }}
  • @endif +
  • Centre: - @if( Auth::user()->centres->count() == 1 ) - {{ Auth::user()->centre->name }} - @elseif (Auth::user()->centres->count() > 1) -
    - {!! method_field('put') !!} - {!! csrf_field() !!} - + @foreach (Auth::user()->centres as $centre) + - @endforeach - -
    - @else - Unknown - @endif + @selected(session('CentreUserCurrentCentreId') === $centre->id) + >{{ $centre->name }} + @endforeach + + + @endswitch
- @endif + @endauth
diff --git a/tests/Unit/Listeners/CentreUserAuthenticatedTest.php b/tests/Unit/Listeners/CentreUserAuthenticatedTest.php index 1c3d5866d..bb98d043f 100644 --- a/tests/Unit/Listeners/CentreUserAuthenticatedTest.php +++ b/tests/Unit/Listeners/CentreUserAuthenticatedTest.php @@ -8,7 +8,6 @@ use App\Sponsor; use Illuminate\Auth\Events\Authenticated; use Illuminate\Foundation\Testing\RefreshDatabase; -use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Session; use Tests\TestCase; @@ -22,8 +21,6 @@ class CentreUserAuthenticatedTest extends TestCase private CentreUser $centreUser; - private Centre $centre; - protected function setUp(): void { parent::setUp(); @@ -33,17 +30,15 @@ protected function setUp(): void Session::forget(self::KEY); $this->sponsor = factory(Sponsor::class)->create(); - $this->centre = factory(Centre::class)->create(['sponsor_id' => $this->sponsor->id]); + factory(Centre::class, 2)->create(['sponsor_id' => $this->sponsor->id]); $this->centreUser = factory(CentreUser::class)->create(); - $this->centreUser->centres()->attach($this->centre->id, ['homeCentre' => true]); + $this->centreUser->centres()->attach([1 => ['homeCentre' => false], 2 => ['homeCentre' => true]]); } public function testItDoesNothingForNonCentreUsers(): void { - Config::set('arc.default_to_home_centre', true); - $listener = new CentreUserAuthenticated(); $nonCentreUser = new class () { @@ -56,38 +51,20 @@ public function testItDoesNothingForNonCentreUsers(): void } - public function testItSetsSessionToAllWhenConfigIsFalseAndKeyIsMissing(): void + public function testItSetsSessionToHomeCentreWhenKeyIsMissing(): void { - Config::set('arc.default_to_home_centre', false); - $listener = new CentreUserAuthenticated(); $user = $this->centreUser; $listener->handle(new Authenticated('store', $user)); - $this->assertSame('all', Session::get(self::KEY)); + $this->assertSame($user->homeCentre->id, Session::get(self::KEY)); } - public function testItSetsSessionToHomeCentreIdWhenConfigIsTrueAndKeyIsMissing(): void + public function testItSetsSessionToNullWhenHomeCentreIsNull(): void { - Config::set('arc.default_to_home_centre', true); - - $listener = new CentreUserAuthenticated(); - - $user = $this->centreUser; - - $listener->handle(new Authenticated('store', $user)); - - $this->assertSame($this->centre->id, Session::get(self::KEY)); - } - - - public function testItSetsSessionToNullWhenConfigIsTrueAndHomeCentreIsNull(): void - { - Config::set('arc.default_to_home_centre', true); - // make a centreUser, don't set up a homeCentre $user = factory(CentreUser::class)->create(); @@ -102,15 +79,11 @@ public function testItSetsSessionToNullWhenConfigIsTrueAndHomeCentreIsNull(): vo public function testItDoesNotOverrideExistingSessionValue(): void { - Config::set('arc.default_to_home_centre', true); - Session::put(self::KEY, 'existing'); $listener = new CentreUserAuthenticated(); - $user = $this->centreUser; - - $listener->handle(new Authenticated('store', $user)); + $listener->handle(new Authenticated('store', $this->centreUser)); $this->assertSame('existing', Session::get(self::KEY)); } From 8a6fda9c8dd2c17fa51300efbcb908af0897081d Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 14 Apr 2026 22:40:19 +0100 Subject: [PATCH 090/168] fix: redirects and type comparisons --- app/Http/Controllers/Store/SessionController.php | 4 ++-- resources/views/store/partials/masthead.blade.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Store/SessionController.php b/app/Http/Controllers/Store/SessionController.php index 2d244ddc4..5745db5c9 100644 --- a/app/Http/Controllers/Store/SessionController.php +++ b/app/Http/Controllers/Store/SessionController.php @@ -12,7 +12,7 @@ public function update(StoreUpdateSessionRequest $request): RedirectResponse { // Set session session(['CentreUserCurrentCentreId' => $request->input('centre')]); - // redirect to a specific place - return redirect()->route('store.registration.index'); + // redirect to the prior route + return redirect(url()->previous()); } } diff --git a/resources/views/store/partials/masthead.blade.php b/resources/views/store/partials/masthead.blade.php index 59e2070f7..910ef12d6 100644 --- a/resources/views/store/partials/masthead.blade.php +++ b/resources/views/store/partials/masthead.blade.php @@ -42,7 +42,7 @@ @foreach (Auth::user()->centres as $centre) @endforeach From 634bdc032fb03475cf91fccf81d35aec4f759024 Mon Sep 17 00:00:00 2001 From: charles strange Date: Tue, 14 Apr 2026 23:09:22 +0100 Subject: [PATCH 091/168] fix: search page needs a filter back --- .../Store/RegistrationController.php | 5 +- .../views/store/index_registration.blade.php | 189 ++++++++++-------- 2 files changed, 113 insertions(+), 81 deletions(-) diff --git a/app/Http/Controllers/Store/RegistrationController.php b/app/Http/Controllers/Store/RegistrationController.php index 4b9b838a8..664c0927e 100644 --- a/app/Http/Controllers/Store/RegistrationController.php +++ b/app/Http/Controllers/Store/RegistrationController.php @@ -88,10 +88,9 @@ public function index(Request $request): View|Factory|Application } // only for cc users with access to more than 1 centre - if ($user->centres->count() > 1) { - // get the centre_id from the masthead dropdown which is set by session (so we can filter reg selection) + if ($user->centres->count() > 1 && $request->boolean('filter_by_centre')) { $filtered_centre_id = session('CentreUserCurrentCentreId'); - if ($filtered_centre_id && $filtered_centre_id !== "all") { + if ($filtered_centre_id) { $q = $q->where('centre_id', '=', $filtered_centre_id); } } diff --git a/resources/views/store/index_registration.blade.php b/resources/views/store/index_registration.blade.php index 5595e3984..35f4477f9 100644 --- a/resources/views/store/index_registration.blade.php +++ b/resources/views/store/index_registration.blade.php @@ -4,93 +4,127 @@ @section('content') - @include('store.partials.navbar', ['headerTitle' => 'Search for a ' . ($programme ? 'household' : 'family')]) - @includeWhen(Session::has('message'), 'store.partials.success') + @include('store.partials.navbar', [ + 'headerTitle' => 'Search for a ' . ($programme ? 'household' : 'family') + ]) + + @if (session('message')) + @include('store.partials.success') + @endif + + @endsection From 3ec265e4991b1ba19b6ea30186edd76e1aeaac1e Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 15 Apr 2026 00:07:15 +0100 Subject: [PATCH 092/168] fix: search page needs a filter back, and it's searching made more efficient --- .../Store/RegistrationController.php | 238 ++++++++---------- app/Registration.php | 62 +++-- tests/Unit/Routes/StoreRoutesTest.php | 30 +-- 3 files changed, 158 insertions(+), 172 deletions(-) diff --git a/app/Http/Controllers/Store/RegistrationController.php b/app/Http/Controllers/Store/RegistrationController.php index 664c0927e..8d3fa6539 100644 --- a/app/Http/Controllers/Store/RegistrationController.php +++ b/app/Http/Controllers/Store/RegistrationController.php @@ -13,184 +13,147 @@ use App\Services\VoucherEvaluator\EvaluatorFactory; use App\Services\VoucherEvaluator\Valuation; use App\User; -use Auth; use Carbon\Carbon; -use DB; use HighSolutions\LaravelSearchy\Facades\Searchy; use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\View; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Foundation\Application; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Arr; -use Log; +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Stringable; use PDF; use Throwable; class RegistrationController extends Controller { - /** - * List all the Registrations (search-ably) - * - * This is a con. It only lists the registrations available to a User's CC's Sponsor - * This means a User can see the Registrations in his 'neighbour' CCs under a Sponsor - * - * Also, the view contains the search functionality. - * - */ public function index(Request $request): View|Factory|Application { - // Masthead bit /** @var User $user */ $user = Auth::user(); - $data = [ - 'user_name' => $user->name, - 'centre_name' => $user->centre?->name, - 'programme' => $user->centre?->sponsor?->programme, - ]; - // get the inputs - $family_name = $request->get('family_name'); - $fuzzy = $request->get('fuzzy'); + $familyName = $request->string('family_name'); + $fuzzy = $request->boolean('fuzzy'); + $descending = $request->input('direction') === 'desc'; - // Slightly roundabout method of getting the permitted centres to poll - $neighbour_centre_ids = $user - ->relevantCentres() - ->pluck('id') - ->toArray(); - - // get primary carers - $pri_carers = Carer::query() - ->selectRaw('MIN(carers.id) AS min_id') - ->whereIn('carers.family_id', function ($q) use ($neighbour_centre_ids) { - // limited to families that have registration in our centres - $q->select('registrations.family_id') - ->from('registrations') - ->whereIn('registrations.centre_id', $neighbour_centre_ids) - ->distinct(); - }) - ->groupBy('carers.family_id') - ->pluck('min_id') - ->toArray(); + $neighbourCentreIds = $user->relevantCentres()->pluck('id'); - // pick a search type - $filtered_family_ids = $fuzzy - ? $this->fuzzySearch($family_name, $pri_carers) - : $this->exactSearch($family_name, $pri_carers); + $query = Registration::query() + ->withPrimaryCarer() + ->whereIn('registrations.centre_id', $neighbourCentreIds); - //find the registrations - $q = Registration::query(); + if ( + $user->centres->count() > 1 && + $request->boolean('filter_by_centre') && + ($centreId = session('CentreUserCurrentCentreId')) + ) { + $query->where('registrations.centre_id', $centreId); + } - if (!empty($neighbour_centre_ids)) { - $q = $q->whereIn('centre_id', $neighbour_centre_ids); + if (!$request->boolean('families_left')) { + $query->WhereActiveFamily(); } - // only for cc users with access to more than 1 centre - if ($user->centres->count() > 1 && $request->boolean('filter_by_centre')) { - $filtered_centre_id = session('CentreUserCurrentCentreId'); - if ($filtered_centre_id) { - $q = $q->where('centre_id', '=', $filtered_centre_id); + // Fuzzy is MySQL-only and returns Searchy-ranked IDs whose order we must + // preserve — handle separately since that requires PHP-side pagination. + if (config('database.connections.' . config('database.default') . '.driver') === 'mysql') { + if ($fuzzy && $familyName->isNotEmpty()) { + return $this->renderFuzzy($request, $user, $query, $familyName, $neighbourCentreIds); } } - if (!empty($filtered_family_ids)) { - $q = $q->whereIn('family_id', $filtered_family_ids) - // Somehow, whereIn re-orders the filtered array into numeric order. - // this would be the "cheap" solution, IF sqlite supported FIELD so we could test that. - // ->orderByRaw(DB::raw("FIELD(family_id, " .implode(',', $filtered_family_ids). ")")); - ; + // Standard path: everything resolved in SQL. + if ($familyName->isNotEmpty()) { + $query->filterByCarerName((string) $familyName); } - // Check if the request asks us to display inactive families - $q = $request->get('families_left') ? $q : $q->WhereActiveFamily(); + $query->orderByCarerName($descending); - // Check if the request should filter by centre - $q = $request->get('centre') ? $q->where('centre_id', $request->get('centre')) : $q; + $registrations = $query + ->WithFullFamily() + ->paginate(perPage: 10) + ->withQueryString(); - // This isn't ideal as it relies on getting all the families, then sorting them. - // However, the whereIn statements above destroy any sorted order on family_ids. - $reg_models = $q->WithFullFamily() - ->get() + return view('store.index_registration', [ + 'user_name' => $user->name, + 'centre_name' => $user->centre?->name, + 'programme' => $user->centre?->sponsor?->programme, + 'registrations' => $registrations, + 'fuzzy' => false, + ]); + } + + /** + * Fuzzy (MySQL-only) path. Searchy returns family IDs in relevance order; + * we preserve that order via PHP-side pagination. + */ + private function renderFuzzy( + Request $request, + User $user, + Builder $query, + Stringable $familyName, + Collection $neighbourCentreIds, + ): View|Factory|Application { + + // Searchy returns relevance-ranked results as an array of stdClass objects + $rankedFamilyIds = collect( + Searchy::search('carers') + ->fields('name') + ->query((string)$familyName) + ->get() + )->pluck('family_id')->toArray(); + + // Scope down to only those family_ids visible in the permitted centres + $permittedFamilyIds = Registration::query() + ->whereIn('family_id', $rankedFamilyIds) + ->whereIn('centre_id', $neighbourCentreIds) + ->pluck('family_id') + ->toArray(); + + // Re-sort by Searchy's original relevance ranking, dropping unpermitted ids + $familyIds = collect($rankedFamilyIds) + ->filter(function (int $id) use ($permittedFamilyIds) { + return in_array($id, $permittedFamilyIds, strict: true); + }) ->values() - ->sortBy('family.pri_carer', SORT_NATURAL, $request->get('direction') === 'desc'); + ->toArray(); + + $all = $query + ->whereIn('registrations.family_id', $familyIds) + ->WithFullFamily() + ->get() + ->sortBy(function ($reg) use ($familyIds) { + return array_search($reg->family_id, $familyIds); + }) + ->values(); - // throw it into a paginator. $page = LengthAwarePaginator::resolveCurrentPage(); - $perPage = 10; - $offset = ($page * $perPage) - $perPage; + $registrations = new LengthAwarePaginator( - $reg_models->slice($offset, $perPage), - $reg_models->count(), - $perPage, - $page, - [ + items: $all->forPage($page, 10), + total: $all->count(), + perPage: 10, + currentPage: $page, + options: [ 'path' => LengthAwarePaginator::resolveCurrentPath(), - 'query' => array_except($request->query(), 'page'), + 'query' => $request->except('page'), ] ); - $data = array_merge( - $data, - [ - 'registrations' => $registrations, - 'fuzzy' => (bool)$fuzzy, - ] - ); - return view('store.index_registration', $data); - } - - private function fuzzySearch($family_name, $pri_carers): array - { - // Get the current database driver - $connection = config('database.default'); - $driver = config("database.connections.$connection.driver"); - - if ($driver === 'mysql') { - // We can use Searchy for mysql; defaults to "fuzzy" search; - // results are a collection of basic objects, but we can still "pluck()" - $filtered_family_ids = Searchy::search('carers') - ->fields('name') - ->query($family_name) - ->getQuery() - ->whereIn('id', $pri_carers) - ->pluck('family_id') - ->toArray(); - } else { - // We may not be able to use Searchy, so we default to unfuzzy. - $filtered_family_ids = $this->exactSearch($family_name, $pri_carers); - } - - return $filtered_family_ids; - } - - private function exactSearch($family_name, $pri_carers): array - { - $carers = Carer::query() - ->where('name', 'LIKE', "%$family_name%") - ->whereIn('id', $pri_carers) - ->get(); - - $startsWithExact = []; - $wholeWord = []; - $theRest = []; - - foreach ($carers as $carer) { - $names = array_map('strtolower', explode(" ", $carer->name)); - - if (count($names) !== 0) { - if (strtolower($names[0]) === strtolower($family_name)) { - $startsWithExact[] = $carer->family_id; - } elseif (in_array($family_name, $names)) { - $wholeWord[] = $carer->family_id; - } else { - $theRest[] = $carer->family_id; - } - } - } - - return array_merge($startsWithExact, $wholeWord, $theRest); + return view('store.index_registration', [ + 'user_name' => $user->name, + 'centre_name' => $user->centre?->name, + 'programme' => $user->centre?->sponsor?->programme, + 'registrations' => $registrations, + 'fuzzy' => true, + ]); } /** @@ -523,7 +486,6 @@ static function (string $name): Carer { $family->carers()->saveMany($carers); $family->children()->saveMany($children); $registration->family()->associate($family); - // TODO - BUGWATCH! this will default to users, default centre if the selector is set to "all" $registration->centre()->associate(Auth::user()->centre); $registration->save(); }); diff --git a/app/Registration.php b/app/Registration.php index e60729449..0fed5b0ca 100644 --- a/app/Registration.php +++ b/app/Registration.php @@ -8,10 +8,13 @@ use App\Traits\Evaluable; use Carbon\Carbon; use Eloquent; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; +use App\Centre; +use App\Family; /** * @mixin Eloquent @@ -77,8 +80,6 @@ public function getEvaluator(): AbstractEvaluator /** * Works out if a Registration can be counted as "Active" - * - * @return bool */ public function isActive(): bool { @@ -111,22 +112,18 @@ public function isActive(): bool /** * Get the Registration's Family - * - * @return BelongsTo */ public function family(): BelongsTo { - return $this->belongsTo('App\Family'); + return $this->belongsTo(Family::class); } /** * Get the Registration's Centre - * - * @return BelongsTo */ public function centre(): BelongsTo { - return $this->belongsTo('App\Centre'); + return $this->belongsTo(Centre::class); } /** @@ -135,7 +132,7 @@ public function centre(): BelongsTo * * @return Model */ - public function currentBundle() + public function currentBundle(): Model { $bundle = $this->bundles() ->where('disbursed_at', null) @@ -166,10 +163,8 @@ public function bundles(): HasMany /** * Fetches the Registrations full Family and dependent models. - * @param $query - * @return mixed */ - public function scopeWithFullFamily($query) + public function scopeWithFullFamily(Builder $query): Builder { return $query->with([ // This may not be efficient, but it is convenient for ordering when required. @@ -183,10 +178,8 @@ public function scopeWithFullFamily($query) /** * Fetches only Registrations with an Active Family - * @param $query - * @return mixed */ - public function scopeWhereActiveFamily($query) + public function scopeWhereActiveFamily(Builder $query): Builder { return $query->whereHas('family', function ($q) { $q->whereNull('leaving_on'); @@ -200,4 +193,43 @@ public function lastBundle(): HasOne ->whereNotNull('disbursed_at') ->latest('disbursed_at'); } + + /** + * Join the primary carer (MIN id per family) directly into the query, + * so we can filter and sort by carer name in SQL rather than PHP. + */ + public function scopeWithPrimaryCarer(Builder $query): Builder + { + return $query + ->select('registrations.*') + ->joinSub( + Carer::query() + ->selectRaw('MIN(id) AS id, family_id') + ->groupBy('family_id'), + 'pri_carers', + 'pri_carers.family_id', '=', 'registrations.family_id' + ) + ->join('carers', 'carers.id', '=', 'pri_carers.id'); + } + + public function scopeOrderByCarerName(Builder $query, bool $descending = false): Builder + { + return $query->orderBy('carers.name', $descending ? 'desc' : 'asc'); + } + + public function scopeFilterByCarerName(Builder $query, string $term): Builder + { + return $query + ->where('carers.name', 'LIKE', "%{$term}%") + ->orderByRaw( + "CASE + WHEN LOWER(carers.name) = LOWER(?) THEN 0 + WHEN LOWER(carers.name) LIKE LOWER(?) THEN 1 + WHEN LOWER(carers.name) LIKE LOWER(?) + OR LOWER(carers.name) LIKE LOWER(?) THEN 2 + ELSE 3 + END", + [$term, "{$term} %", "% {$term} %", "% {$term}"] + ); + } } diff --git a/tests/Unit/Routes/StoreRoutesTest.php b/tests/Unit/Routes/StoreRoutesTest.php index b7085a7d3..4acc766a0 100644 --- a/tests/Unit/Routes/StoreRoutesTest.php +++ b/tests/Unit/Routes/StoreRoutesTest.php @@ -16,33 +16,25 @@ class StoreRoutesTest extends StoreTestCase { use RefreshDatabase; - /** @var CentreUser $fmUser */ - private $fmUser; + private CentreUser $fmUser; - /** @var CentreUser $centreUser */ - private $centreUser; + private CentreUser $centreUser; - /** @var CentreUser $downlaoderUser */ - private $downloaderUser; + private CentreUser $downloaderUser; - /** @var CentreUser $neighbourUser */ - private $neighbourUser; + private CentreUser $neighbourUser; - /** @var CentreUser $foreignUser */ - private $unrelatedUser; + private CentreUser $unrelatedUser; - /** @var Centre $centre */ - private $centre; + private Centre $centre; - /** @var Centre $neighbourCentre */ - private $neighbourCentre; + private Centre $neighbourCentre; - /** @var Centre $unrelatedCentre */ - private $unrelatedCentre; + private Centre $unrelatedCentre; - private $dashboardRoute; + private string $dashboardRoute; - private $searchRegistrationsRoute; + private string $searchRegistrationsRoute; public function setUp(): void { @@ -437,7 +429,7 @@ public function testSessionUpdateGate(): void // return to prior route $this->followRedirects() - ->seePageIs($this->searchRegistrationsRoute) + ->seePageIs($this->dashboardRoute) ->assertResponseStatus(200); } From 49f4279fa61d2b6b29f9d99f1cfecdb2a5d176f9 Mon Sep 17 00:00:00 2001 From: charles strange Date: Wed, 15 Apr 2026 00:08:12 +0100 Subject: [PATCH 093/168] fix: chevron does not turn off the checkboxes --- .../views/store/partials/sortableChevron.blade.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/resources/views/store/partials/sortableChevron.blade.php b/resources/views/store/partials/sortableChevron.blade.php index 35b9c3104..f4b9e4758 100644 --- a/resources/views/store/partials/sortableChevron.blade.php +++ b/resources/views/store/partials/sortableChevron.blade.php @@ -1,9 +1,12 @@ Sort by this column. - \ No newline at end of file + From e06a2502955531d898e0747343a469d293ca621c Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 16 Apr 2026 11:16:48 +0100 Subject: [PATCH 094/168] fix: sorting and implement a basic strategy for fetching and oirdering. --- .../Store/RegistrationController.php | 95 ++++++++++--------- .../store/partials/sortableChevron.blade.php | 2 +- 2 files changed, 53 insertions(+), 44 deletions(-) diff --git a/app/Http/Controllers/Store/RegistrationController.php b/app/Http/Controllers/Store/RegistrationController.php index 8d3fa6539..d275976b0 100644 --- a/app/Http/Controllers/Store/RegistrationController.php +++ b/app/Http/Controllers/Store/RegistrationController.php @@ -34,7 +34,7 @@ class RegistrationController extends Controller { - public function index(Request $request): View|Factory|Application + public function index(Request $request): Factory|View|RedirectResponse { /** @var User $user */ $user = Auth::user(); @@ -42,10 +42,9 @@ public function index(Request $request): View|Factory|Application $familyName = $request->string('family_name'); $fuzzy = $request->boolean('fuzzy'); $descending = $request->input('direction') === 'desc'; - $neighbourCentreIds = $user->relevantCentres()->pluck('id'); - $query = Registration::query() + $baseQuery = Registration::query() ->withPrimaryCarer() ->whereIn('registrations.centre_id', $neighbourCentreIds); @@ -54,55 +53,69 @@ public function index(Request $request): View|Factory|Application $request->boolean('filter_by_centre') && ($centreId = session('CentreUserCurrentCentreId')) ) { - $query->where('registrations.centre_id', $centreId); + $baseQuery->where('registrations.centre_id', $centreId); } if (!$request->boolean('families_left')) { - $query->WhereActiveFamily(); + $baseQuery->WhereActiveFamily(); } - // Fuzzy is MySQL-only and returns Searchy-ranked IDs whose order we must - // preserve — handle separately since that requires PHP-side pagination. - if (config('database.connections.' . config('database.default') . '.driver') === 'mysql') { - if ($fuzzy && $familyName->isNotEmpty()) { - return $this->renderFuzzy($request, $user, $query, $familyName, $neighbourCentreIds); - } + $useFuzzy = $fuzzy + && $familyName->isNotEmpty() + && config('database.connections.' . config('database.default') . '.driver') === 'mysql'; + + [$registrations, $resolvedFuzzy] = $useFuzzy + ? [$this->fetchFuzzy($request, $baseQuery, $familyName, $neighbourCentreIds, $descending), true] + : [$this->fetchExact($baseQuery, $familyName, $descending), false]; + + if ($registrations->currentPage() > $registrations->lastPage()) { + return redirect()->to( + $registrations->url($registrations->lastPage()) + ); } - // Standard path: everything resolved in SQL. + return view('store.index_registration', [ + 'user_name' => $user->name, + 'centre_name' => $user->centre?->name, + 'programme' => $user->centre?->sponsor?->programme, + 'registrations' => $registrations, + 'fuzzy' => $resolvedFuzzy, + ]); + } + + /** + * Exact-match strategy: everything resolved in SQL. + * Returns a paginator ready for the view. + */ + private function fetchExact( + Builder $query, + Stringable $familyName, + bool $descending, + ): LengthAwarePaginator { if ($familyName->isNotEmpty()) { - $query->filterByCarerName((string) $familyName); + $query->filterByCarerName((string)$familyName); } $query->orderByCarerName($descending); - $registrations = $query + return $query ->WithFullFamily() ->paginate(perPage: 10) ->withQueryString(); - - return view('store.index_registration', [ - 'user_name' => $user->name, - 'centre_name' => $user->centre?->name, - 'programme' => $user->centre?->sponsor?->programme, - 'registrations' => $registrations, - 'fuzzy' => false, - ]); } /** - * Fuzzy (MySQL-only) path. Searchy returns family IDs in relevance order; - * we preserve that order via PHP-side pagination. + * Fuzzy strategy (MySQL-only): Searchy ranks IDs by relevance; + * ordering is preserved via PHP-side pagination. + * Returns a paginator ready for the view. */ - private function renderFuzzy( + private function fetchFuzzy( Request $request, - User $user, Builder $query, Stringable $familyName, Collection $neighbourCentreIds, - ): View|Factory|Application { - - // Searchy returns relevance-ranked results as an array of stdClass objects + bool $descending, + ): LengthAwarePaginator { $rankedFamilyIds = collect( Searchy::search('carers') ->fields('name') @@ -110,14 +123,12 @@ private function renderFuzzy( ->get() )->pluck('family_id')->toArray(); - // Scope down to only those family_ids visible in the permitted centres $permittedFamilyIds = Registration::query() ->whereIn('family_id', $rankedFamilyIds) ->whereIn('centre_id', $neighbourCentreIds) ->pluck('family_id') ->toArray(); - // Re-sort by Searchy's original relevance ranking, dropping unpermitted ids $familyIds = collect($rankedFamilyIds) ->filter(function (int $id) use ($permittedFamilyIds) { return in_array($id, $permittedFamilyIds, strict: true); @@ -125,18 +136,24 @@ private function renderFuzzy( ->values() ->toArray(); + $positionMap = array_flip($familyIds); + $all = $query ->whereIn('registrations.family_id', $familyIds) ->WithFullFamily() ->get() - ->sortBy(function ($reg) use ($familyIds) { - return array_search($reg->family_id, $familyIds); - }) + ->sortBy( + function ($reg) use ($positionMap) { + return $positionMap[$reg->family_id] ?? PHP_INT_MAX; + }, + SORT_REGULAR, + $descending, + ) ->values(); $page = LengthAwarePaginator::resolveCurrentPage(); - $registrations = new LengthAwarePaginator( + return new LengthAwarePaginator( items: $all->forPage($page, 10), total: $all->count(), perPage: 10, @@ -146,14 +163,6 @@ private function renderFuzzy( 'query' => $request->except('page'), ] ); - - return view('store.index_registration', [ - 'user_name' => $user->name, - 'centre_name' => $user->centre?->name, - 'programme' => $user->centre?->sponsor?->programme, - 'registrations' => $registrations, - 'fuzzy' => true, - ]); } /** diff --git a/resources/views/store/partials/sortableChevron.blade.php b/resources/views/store/partials/sortableChevron.blade.php index f4b9e4758..2c36364f7 100644 --- a/resources/views/store/partials/sortableChevron.blade.php +++ b/resources/views/store/partials/sortableChevron.blade.php @@ -3,7 +3,7 @@ route($route, array_merge( request()->query(), [ - 'orderBy' => $orderBy, + 'orderBy' => $orderBy, 'direction' => $direction === 'desc' ? 'asc' : 'desc', ] )) From 8e6fefccc4ef64a9caacc58496a3ccdde78f53cc Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 16 Apr 2026 12:24:55 +0100 Subject: [PATCH 095/168] fix: write tests and update carer model --- app/Carer.php | 3 +- .../Store/RegistrationControllerFuzzyTest.php | 226 ++++++++ tests/Feature/Store/SearchPageTest.php | 524 ++++++++---------- tests/MysqlStoreTestCase.php | 2 +- tests/Unit/Models/RegistrationModelTest.php | 211 +++++++ 5 files changed, 671 insertions(+), 295 deletions(-) create mode 100644 tests/Feature/Store/RegistrationControllerFuzzyTest.php diff --git a/app/Carer.php b/app/Carer.php index 6eda87d5c..4d5e01ed8 100644 --- a/app/Carer.php +++ b/app/Carer.php @@ -36,7 +36,8 @@ class Carer extends LazySecureModel implements CipherSweetEncrypted 'ethnicity', 'language', 'emailsecret', - 'telnosecret' + 'telnosecret', + 'family_id', ]; public static function configureCipherSweet(EncryptedRow $encryptedRow): void diff --git a/tests/Feature/Store/RegistrationControllerFuzzyTest.php b/tests/Feature/Store/RegistrationControllerFuzzyTest.php new file mode 100644 index 000000000..22edccb5f --- /dev/null +++ b/tests/Feature/Store/RegistrationControllerFuzzyTest.php @@ -0,0 +1,226 @@ +create(); + $user->centres()->attach($centre->id, ['homeCentre' => true]); + return $user; + } + + private function setCarerName(Registration $reg, string $name): void + { + $reg->family->carers()->orderBy('id')->first()->update(['name' => $name]); + } + + /** + * Mock Searchy's fluent chain to return the provided stdClass results array. + * Each element must have a `family_id` property. + */ + private function mockSearchy(array $results): void + { + $chain = Mockery::mock(); + $chain->shouldReceive('fields')->atLeast()->once()->andReturnSelf(); + $chain->shouldReceive('query')->atLeast()->once()->andReturnSelf(); + $chain->shouldReceive('get')->atLeast()->once()->andReturn($results); + + Searchy::shouldReceive('search')->with('carers')->atLeast()->once()->andReturn($chain); + } + + // ── Strategy selection ──────────────────────────────────────────────────── + + public function testIndexUsesFuzzyPathWhenFuzzySetAndDriverIsMysql(): void + { + $centre = factory(Centre::class)->create(); + $user = $this->userInCentre($centre); + $reg = factory(Registration::class)->create(['centre_id' => $centre->id]); + + $this->mockSearchy([(object)['family_id' => $reg->family_id]]); + + $name = $reg->family->carers()->orderBy('id')->first()->name; + + $this->actingAs($user, 'store') + ->visit(URL::route('store.registration.index', [ + 'family_name' => $name, + 'fuzzy' => '1', + ])); + + // Searchy mock enforces it was called (once assertions in mockSearchy). + // Registration visible confirms the fuzzy path completed successfully. + $this->see(URL::route('store.registration.edit', $reg)); + } + + // ── Relevance ordering ──────────────────────────────────────────────────── + + public function testFuzzyResultsReturnedInSearchyRelevanceOrder(): void + { + $centre = factory(Centre::class)->create(); + $user = $this->userInCentre($centre); + + $regs = factory(Registration::class, 3)->create(['centre_id' => $centre->id]); + $this->setCarerName($regs[0], 'Smith Charlie'); + $this->setCarerName($regs[1], 'Smith Alice'); + $this->setCarerName($regs[2], 'Smith Bob'); + + // Searchy ranks: reg[2] first, reg[0] second, reg[1] third + $this->mockSearchy([ + (object)['family_id' => $regs[2]->family_id], + (object)['family_id' => $regs[0]->family_id], + (object)['family_id' => $regs[1]->family_id], + ]); + + $this->actingAs($user, 'store') + ->visit(URL::route('store.registration.index', [ + 'family_name' => 'Smith', + 'fuzzy' => '1', + ])); + + $this->seeInElementAtPos('td.pri_carer', 'Smith Bob', 0); + $this->seeInElementAtPos('td.pri_carer', 'Smith Charlie', 1); + $this->seeInElementAtPos('td.pri_carer', 'Smith Alice', 2); + } + + public function testFuzzyResultsReversedWhenDirectionIsDesc(): void + { + $centre = factory(Centre::class)->create(); + $user = $this->userInCentre($centre); + + $regs = factory(Registration::class, 3)->create(['centre_id' => $centre->id]); + $this->setCarerName($regs[0], 'Smith Charlie'); + $this->setCarerName($regs[1], 'Smith Alice'); + $this->setCarerName($regs[2], 'Smith Bob'); + + // Searchy ranks: reg[2] (rank 0), reg[0] (rank 1), reg[1] (rank 2) + $this->mockSearchy([ + (object)['family_id' => $regs[2]->family_id], + (object)['family_id' => $regs[0]->family_id], + (object)['family_id' => $regs[1]->family_id], + ]); + + $this->actingAs($user, 'store') + ->visit(URL::route('store.registration.index', [ + 'family_name' => 'Smith', + 'fuzzy' => '1', + 'direction' => 'desc', + ])); + + // Descending inverts Searchy rank: reg[1] (rank 2), reg[0] (rank 1), reg[2] (rank 0) + $this->seeInElementAtPos('td.pri_carer', 'Smith Alice', 0); + $this->seeInElementAtPos('td.pri_carer', 'Smith Charlie', 1); + $this->seeInElementAtPos('td.pri_carer', 'Smith Bob', 2); + } + + // ── Centre permission scoping ───────────────────────────────────────────── + + public function testFuzzyExcludesFamiliesOutsidePermittedCentres(): void + { + $sponsor = factory(Sponsor::class)->create(); + $myCentre = factory(Centre::class)->create(['sponsor_id' => $sponsor->id]); + $otherCentre = factory(Centre::class)->create([ + 'sponsor_id' => factory(Sponsor::class)->create()->id, + ]); + + $user = $this->userInCentre($myCentre); + $myReg = factory(Registration::class)->create(['centre_id' => $myCentre->id]); + $otherReg = factory(Registration::class)->create(['centre_id' => $otherCentre->id]); + + // Searchy returns both; only myReg is within permitted centres + $this->mockSearchy([ + (object)['family_id' => $myReg->family_id], + (object)['family_id' => $otherReg->family_id], + ]); + + $this->actingAs($user, 'store') + ->visit(URL::route('store.registration.index', [ + 'family_name' => 'test', + 'fuzzy' => '1', + ])); + + $this->see(URL::route('store.registration.edit', $myReg)); + $this->dontSee(URL::route('store.registration.edit', $otherReg)); + } + + public function testFuzzyPreservesRelevanceOrderAfterPermissionFiltering(): void + { + $sponsor = factory(Sponsor::class)->create(); + $myCentre = factory(Centre::class)->create(['sponsor_id' => $sponsor->id]); + $otherCentre = factory(Centre::class)->create([ + 'sponsor_id' => factory(Sponsor::class)->create()->id, + ]); + + $user = $this->userInCentre($myCentre); + + $regs = factory(Registration::class, 2)->create(['centre_id' => $myCentre->id]); + $alien = factory(Registration::class)->create(['centre_id' => $otherCentre->id]); + + $this->setCarerName($regs[0], 'Smith Alice'); + $this->setCarerName($regs[1], 'Smith Bob'); + + // Searchy ranks: alien (rank 0), regs[1] (rank 1), regs[0] (rank 2) + // After permission filtering: regs[1] should stay ahead of regs[0] + $this->mockSearchy([ + (object)['family_id' => $alien->family_id], + (object)['family_id' => $regs[1]->family_id], + (object)['family_id' => $regs[0]->family_id], + ]); + + $this->actingAs($user, 'store') + ->visit(URL::route('store.registration.index', [ + 'family_name' => 'Smith', + 'fuzzy' => '1', + ])); + + $this->seeInElementAtPos('td.pri_carer', 'Smith Bob', 0); + $this->seeInElementAtPos('td.pri_carer', 'Smith Alice', 1); + } + + // ── Pagination redirect ─────────────────────────────────────────────────── + + public function testFuzzyRedirectsToLastPageWhenRequestedPageExceedsLastPage(): void + { + $centre = factory(Centre::class)->create(); + $user = $this->userInCentre($centre); + $regs = factory(Registration::class, 3)->create(['centre_id' => $centre->id]); + + $this->mockSearchy( + $regs->map(fn($r) => (object)['family_id' => $r->family_id])->all() + ); + + $this->actingAs($user, 'store') + ->visit(URL::route('store.registration.index', [ + 'family_name' => 'test', + 'fuzzy' => '1', + 'page' => '99', + ])); + + $this->seePageIs(URL::route('store.registration.index', [ + 'family_name' => 'test', + 'fuzzy' => '1', + 'page' => '1', + ])); + } +} diff --git a/tests/Feature/Store/SearchPageTest.php b/tests/Feature/Store/SearchPageTest.php index e64f1bd8d..e7a9d2244 100644 --- a/tests/Feature/Store/SearchPageTest.php +++ b/tests/Feature/Store/SearchPageTest.php @@ -7,349 +7,322 @@ use App\Registration; use App\Sponsor; use Carbon\Carbon; -use Tests\StoreTestCase; use Illuminate\Foundation\Testing\RefreshDatabase; +use Tests\StoreTestCase; use URL; class SearchPageTest extends StoreTestCase { use RefreshDatabase; + // ── Helpers ─────────────────────────────────────────────────────────────── + + private function userInCentre(Centre $centre): CentreUser + { + $user = factory(CentreUser::class)->create(); + $user->centres()->attach($centre->id, ['homeCentre' => true]); + return $user; + } + + /** Force a known name onto the primary (lowest-id) carer of a registration. */ + private function setCarerName(Registration $reg, string $name): void + { + $reg->family->carers()->orderBy('id')->first()->update(['name' => $name]); + } + + // ── Rendering & content ─────────────────────────────────────────────────── public function testItShowsTheLoggedInUser(): void { - // Create some centres - factory(Centre::class, 4)->create(); - - // Create a CentreUser - $centreUser = factory(CentreUser::class)->create([ - "name" => "test user", - "email" => "testuser@example.com", - "password" => bcrypt('test_user_pass'), - ]); - $centreUser->centres()->attach(1, ['homeCentre' => true]); + $centre = factory(Centre::class)->create(); + $centreUser = $this->userInCentre($centre); $this->actingAs($centreUser, 'store') ->visit(URL::route('store.registration.index')) - ->see($centreUser->name) - ; + ->see($centreUser->name); } + public function testItShowsThePrimaryCarerName(): void + { + $centre = factory(Centre::class)->create(); + $centreUser = $this->userInCentre($centre); + $registration = factory(Registration::class)->create(['centre_id' => $centre->id]); + $pri_carer = $registration->family->carers->first(); - public function testItShowsRegistrationsFromNeighbourCentres(): void + $this->actingAs($centreUser, 'store') + ->visit(URL::route('store.registration.index')) + ->see($pri_carer->name); + } + + public function testItShowsTheRVID(): void { - // Create a single Sponsor - $sponsor = factory(Sponsor::class)->create(); + $centre = factory(Centre::class)->create(); + $centreUser = $this->userInCentre($centre); + $registration = factory(Registration::class)->create(['centre_id' => $centre->id]); - // Create centres - $centres = factory(Centre::class, 2)->create([ - "sponsor_id" => $sponsor->id, - ]); + $this->actingAs($centreUser, 'store') + ->visit(URL::route('store.registration.index')) + ->see($registration->family->rvid); + } - $centre1 = $centres->first(); - $centre2 = $centres->last(); + public function testItShowsCentreLabelsForUsersByDefault(): void + { + $centre1 = factory(Centre::class)->create(['name' => 'Tatooine']); + $centre2 = factory(Centre::class)->create(['name' => 'Dagobah']); + $centre3 = factory(Centre::class)->create(['name' => 'Coruscant']); - // Create a CentreUser in Centre 1 - $centreUser = factory(CentreUser::class)->create([ - "name" => "test user", - "email" => "testuser@example.com", - "password" => bcrypt('test_user_pass'), - ]); - $centreUser->centres()->attach($centre1->id, ['homeCentre' => true]); + $centreUser = $this->userInCentre($centre1); - // Make centre1 some registrations - $registrations1 = factory(Registration::class, 4)->create([ - "centre_id" => $centre1->id, - ]); + factory(Registration::class, 4)->create(['centre_id' => $centre1->id]); + factory(Registration::class, 3)->create(['centre_id' => $centre2->id]); + factory(Registration::class, 2)->create(['centre_id' => $centre3->id]); - // Make centre2 some registrations - $registrations2 = factory(Registration::class, 4)->create([ - "centre_id" => $centre2->id, - ]); + $this->actingAs($centreUser, 'store') + ->visit(URL::route('store.registration.index')) + ->see('Tatooine') + ->see('Dagobah') + ->see('Coruscant'); + + $this->assertCount(9, $this->crawler->filter('div.secondary_info')); + } + + public function testAVouchersButtonIsPresent(): void + { + $centre = factory(Centre::class)->create(); + $centreUser = $this->userInCentre($centre); + factory(Registration::class)->create(['centre_id' => $centre->id]); - // Visit the page $this->actingAs($centreUser, 'store') - ->visit(URL::route('store.registration.index')); + ->visit(URL::route('store.registration.index')) + ->see('Vouchers'); + } - $registrations = $registrations1->concat($registrations2); + // ── Centre scoping ──────────────────────────────────────────────────────── + + public function testItShowsRegistrationsFromNeighbourCentres(): void + { + $sponsor = factory(Sponsor::class)->create(); + $centres = factory(Centre::class, 2)->create(['sponsor_id' => $sponsor->id]); + $centre1 = $centres->first(); + $centre2 = $centres->last(); + + $centreUser = $this->userInCentre($centre1); + $registrations = factory(Registration::class, 4)->create(['centre_id' => $centre1->id]) + ->concat(factory(Registration::class, 4)->create(['centre_id' => $centre2->id])); + + $this->actingAs($centreUser, 'store') + ->visit(URL::route('store.registration.index')); - // Check we can see the edit link with the registration ID in it. foreach ($registrations as $registration) { - $edit_url_string = URL::route('store.registration.edit', [ 'registration' => $registration->id]); - $this->see($edit_url_string); + $this->see(URL::route('store.registration.edit', ['registration' => $registration->id])); } } - - public function testItShowsRegistrationsFromMyCentre(): void { - // Create a single Sponsor $sponsor = factory(Sponsor::class)->create(); + $centre = factory(Centre::class)->create(['sponsor_id' => $sponsor->id]); + $centreUser = $this->userInCentre($centre); - // Create centre - $centre = factory(Centre::class)->create([ - "sponsor_id" => $sponsor->id, - ]); + $registrations = factory(Registration::class, 4)->create(['centre_id' => $centre->id]); - // Create a CentreUser in Centre - $centreUser = factory(CentreUser::class)->create([ - "name" => "test user", - "email" => "testuser@example.com", - "password" => bcrypt('test_user_pass'), - ]); - $centreUser->centres()->attach($centre->id, ['homeCentre' => true]); + $this->actingAs($centreUser, 'store') + ->visit(URL::route('store.registration.index')); - // Make centre some registrations - $registrations = factory(Registration::class, 4)->create([ - "centre_id" => $centre->id, + foreach ($registrations as $registration) { + $this->see(URL::route('store.registration.edit', ['registration' => $registration->id])); + } + } + + public function testItDoesNotShowRegistrationsFromUnrelatedCentres(): void + { + $sponsor = factory(Sponsor::class)->create(); + $neighbourCentres = factory(Centre::class, 2)->create(['sponsor_id' => $sponsor->id]); + $alienCentre = factory(Centre::class)->create([ + 'sponsor_id' => factory(Sponsor::class)->create()->id, ]); + $centreUser = $this->userInCentre($neighbourCentres->first()); + + factory(Registration::class, 4)->create(['centre_id' => $neighbourCentres->first()->id]); + factory(Registration::class, 4)->create(['centre_id' => $neighbourCentres->last()->id]); + $alienRegistrations = factory(Registration::class, 4)->create(['centre_id' => $alienCentre->id]); + $this->actingAs($centreUser, 'store') ->visit(URL::route('store.registration.index')); - // Check we can see the edit link with the registration ID in it. - foreach ($registrations as $registration) { - $edit_url_string = URL::route('store.registration.edit', [ 'registration' => $registration->id]); - $this->see($edit_url_string); + foreach ($alienRegistrations as $registration) { + $this->dontSee(URL::route('store.registration.edit', ['registration' => $registration->id])); } } - public function testItDoesNotShowRegistrationsFromUnrelatedCentres(): void + public function testItScopesToSessionCentreWhenFilterByCentreIsSet(): void { - // Create a single Sponsor $sponsor = factory(Sponsor::class)->create(); + $centre1 = factory(Centre::class)->create(['sponsor_id' => $sponsor->id]); + $centre2 = factory(Centre::class)->create(['sponsor_id' => $sponsor->id]); - // Create centres - $neighbour_centres = factory(Centre::class, 2)->create([ - "sponsor_id" => $sponsor->id, - ]); + // User must belong to both centres for the filter_by_centre guard to trigger + $user = factory(CentreUser::class)->create(); + $user->centres()->attach($centre1->id, ['homeCentre' => true]); + $user->centres()->attach($centre2->id, ['homeCentre' => false]); - $alien_centre = factory(Centre::class)->create([ - "sponsor_id" => factory(Sponsor::class)->create()->id, - ]); + $reg1 = factory(Registration::class)->create(['centre_id' => $centre1->id]); + $reg2 = factory(Registration::class)->create(['centre_id' => $centre2->id]); - $centre1 = $neighbour_centres->first(); - $centre2 = $neighbour_centres->last(); + $this->actingAs($user, 'store') + ->withSession(['CentreUserCurrentCentreId' => $centre1->id]) + ->visit(URL::route('store.registration.index', ['filter_by_centre' => '1'])); - // Create a CentreUser in Centre 1 - $centreUser = factory(CentreUser::class)->create([ - "name" => "test user", - "email" => "testuser@example.com", - "password" => bcrypt('test_user_pass'), - ]); - $centreUser->centres()->attach($centre1->id, ['homeCentre' => true]); + $this->see(URL::route('store.registration.edit', $reg1)); + $this->dontSee(URL::route('store.registration.edit', $reg2)); + } - // make centre1 some registrations - factory(Registration::class, 4)->create([ - "centre_id" => $centre1->id, - ]); + public function testItIgnoresCentreFilterForSingleCentreUser(): void + { + // The filter_by_centre guard requires centres->count() > 1; single-centre + // users are unaffected and continue to see their full neighbour set. + $sponsor = factory(Sponsor::class)->create(); + $centre1 = factory(Centre::class)->create(['sponsor_id' => $sponsor->id]); + $centre2 = factory(Centre::class)->create(['sponsor_id' => $sponsor->id]); - // Make centre2 some registrations - factory(Registration::class, 4)->create([ - "centre_id" => $centre2->id, - ]); + $user = $this->userInCentre($centre1); - // Make alien_centre some registrations - $registrations3 = factory(Registration::class, 4)->create([ - "centre_id" => $alien_centre->id, - ]); + $reg1 = factory(Registration::class)->create(['centre_id' => $centre1->id]); + $reg2 = factory(Registration::class)->create(['centre_id' => $centre2->id]); - $this->actingAs($centreUser, 'store') - ->visit(URL::route('store.registration.index')); + $this->actingAs($user, 'store') + ->withSession(['CentreUserCurrentCentreId' => $centre1->id]) + ->visit(URL::route('store.registration.index', ['filter_by_centre' => '1'])); - // Check we can see the edit link with the registration ID in it. - foreach ($registrations3 as $registration) { - $edit_url_string = URL::route('store.registration.edit', [ 'registration' => $registration->id]); - $this->dontSee($edit_url_string); - } + $this->see(URL::route('store.registration.edit', $reg1)); + $this->see(URL::route('store.registration.edit', $reg2)); } + // ── Strategy selection ──────────────────────────────────────────────────── - public function testItShowsThePrimaryCarerName(): void + public function testItUsesExactPathWhenFamilyNameIsAbsent(): void { - - // Create a Centre (and, implicitly a random Sponsor) + // fuzzy=1 with no name → $familyName->isEmpty() → $useFuzzy is false $centre = factory(Centre::class)->create(); + $user = $this->userInCentre($centre); + $reg = factory(Registration::class)->create(['centre_id' => $centre->id]); - // Create a CentreUser - $centreUser = factory(CentreUser::class)->create([ - "name" => "test user", - "email" => "testuser@example.com", - "password" => bcrypt('test_user_pass'), - ]); - $centreUser->centres()->attach($centre->id, ['homeCentre' => true]); + $this->actingAs($user, 'store') + ->visit(URL::route('store.registration.index', ['fuzzy' => '1'])); - // Create a random registration. - $registration = factory(Registration::class)->create([ - "centre_id" => $centre->id, - ]); + // Registration visible confirms the exact path ran without Searchy + $this->see(URL::route('store.registration.edit', $reg)); + } - // Get the primary carer - $pri_carer = $registration->family->carers->first(); + public function testItUsesExactPathWhenDriverIsNotMysql(): void + { + // Default test driver is SQLite; even with fuzzy=1 + name the exact path runs + $centre = factory(Centre::class)->create(); + $user = $this->userInCentre($centre); + $reg = factory(Registration::class)->create(['centre_id' => $centre->id]); + $name = $reg->family->carers()->orderBy('id')->first()->name; - // Spot the Registration Family's primary carer name - $this->actingAs($centreUser, 'store') - ->visit(URL::route('store.registration.index')) - ->see($pri_carer->name); + $this->actingAs($user, 'store') + ->visit(URL::route('store.registration.index', [ + 'family_name' => $name, + 'fuzzy' => '1', + ])); + + $this->see(URL::route('store.registration.edit', $reg)); } + // ── Filtering ───────────────────────────────────────────────────────────── - public function testItShowsTheRVID(): void + public function testItFiltersResultsByCarerNameSubstring(): void { - // Create a Centre $centre = factory(Centre::class)->create(); + $user = $this->userInCentre($centre); + $matching = factory(Registration::class)->create(['centre_id' => $centre->id]); + $other = factory(Registration::class)->create(['centre_id' => $centre->id]); - // Create a CentreUser - $centreUser = factory(CentreUser::class)->create([ - "name" => "test user", - "email" => "testuser@example.com", - "password" => bcrypt('test_user_pass'), - ]); - $centreUser->centres()->attach($centre->id, ['homeCentre' => true]); + $this->setCarerName($matching, 'Zelda Unique'); + $this->setCarerName($other, 'Other Person'); - // Create a random registration with our centre. - $registration = factory(Registration::class)->create([ - "centre_id" => $centre->id, - ]); + $this->actingAs($user, 'store') + ->visit(URL::route('store.registration.index', ['family_name' => 'Zelda'])); - // Spot the Registration family's RVID - $this->actingAs($centreUser, 'store') - ->visit(URL::route('store.registration.index')) - ->see($registration->family->rvid); + $this->see(URL::route('store.registration.edit', $matching)); + $this->dontSee(URL::route('store.registration.edit', $other)); } - + // ── Ordering ────────────────────────────────────────────────────────────── public function testItShowsFamilyPrimaryCarersAlphabetically(): void { - // Create a Centre (and, implicitly a random Sponsor) $centre = factory(Centre::class)->create(); + $centreUser = $this->userInCentre($centre); - // Create a centreUser - $centreUser = factory(CentreUser::class)->create([ - "name" => "test user", - "email" => "testuser@example.com", - "password" => bcrypt('test_user_pass'), - ]); - $centreUser->centres()->attach($centre->id, ['homeCentre' => true]); - - // Create a random registration or 5, which should be well under the limit. - $registrations = factory(Registration::class, 5)->create([ - "centre_id" => $centre->id, - ]); - - //get the primary carers as an array - $pri_carers = $registrations->map(function ($registration) { - return $registration->family->carers->first()->name; - })->toArray(); + $regs = factory(Registration::class, 3)->create(['centre_id' => $centre->id]); + $this->setCarerName($regs[0], 'Charlie'); + $this->setCarerName($regs[1], 'Alice'); + $this->setCarerName($regs[2], 'Bob'); - sort($pri_carers, SORT_NATURAL); - - // Spot the Registration Family's primary carer name $this->actingAs($centreUser, 'store') ->visit(URL::route('store.registration.index')); - $selector = 'td.pri_carer'; - - $content = $this->crawler->filter($selector); - - foreach ($pri_carers as $index => $pri_carer) { - $this->seeInElementAtPos($selector, $pri_carer, $index); - } + $this->seeInElementAtPos('td.pri_carer', 'Alice', 0); + $this->seeInElementAtPos('td.pri_carer', 'Bob', 1); + $this->seeInElementAtPos('td.pri_carer', 'Charlie', 2); } - - public function testItHasTheExpectedResultsPerPage(): void + public function testItOrdersCarerNamesDescending(): void { $centre = factory(Centre::class)->create(); + $centreUser = $this->userInCentre($centre); - // Create a CentreUser - $centreUser = factory(CentreUser::class)->create([ - "name" => "test user", - "email" => "testuser@example.com", - "password" => bcrypt('test_user_pass'), - ]); - $centreUser->centres()->attach($centre->id, ['homeCentre' => true]); - - // Create 10 random registrations. - factory(Registration::class, 10)->create([ - "centre_id" => $centre->id, - ]); + $regs = factory(Registration::class, 3)->create(['centre_id' => $centre->id]); + $this->setCarerName($regs[0], 'Charlie'); + $this->setCarerName($regs[1], 'Alice'); + $this->setCarerName($regs[2], 'Bob'); $this->actingAs($centreUser, 'store') - ->visit(URL::route('store.registration.index')) - ; - + ->visit(URL::route('store.registration.index', ['direction' => 'desc'])); - // Spot and count the Registrations Family's primary carer names - $selector = 'td.pri_carer'; - $this->assertCount(10, $this->crawler->filter($selector)); + $this->seeInElementAtPos('td.pri_carer', 'Charlie', 0); + $this->seeInElementAtPos('td.pri_carer', 'Bob', 1); + $this->seeInElementAtPos('td.pri_carer', 'Alice', 2); } + // ── Pagination ──────────────────────────────────────────────────────────── - public function testItShowsCentreLabelsForUsersByDefault(): void + public function testItHasTheExpectedResultsPerPage(): void { - // Create some centres - $centre1 = factory(Centre::class)->create([ - "name" => "Tatooine" - ]); - $centre2 = factory(Centre::class)->create([ - "name" => "Dagobah" - ]); - $centre3 = factory(Centre::class)->create([ - "name" => "Coruscant" - ]); + $centre = factory(Centre::class)->create(); + $centreUser = $this->userInCentre($centre); + factory(Registration::class, 10)->create(['centre_id' => $centre->id]); - // Create a Centre User - $centreUser = factory(CentreUser::class)->create([ - "name" => "test user", - "email" => "testuser@example.com", - "password" => bcrypt('test_user_pass'), - ]); - $centreUser->centres()->attach($centre1->id, ['homeCentre' => true]); + $this->actingAs($centreUser, 'store') + ->visit(URL::route('store.registration.index')); - // Create some registrations in different centres - factory(Registration::class, 4)->create([ - "centre_id" => $centre1->id, - ]); - factory(Registration::class, 3)->create([ - "centre_id" => $centre2->id, - ]); - factory(Registration::class, 2)->create([ - "centre_id" => $centre3->id, - ]); + $this->assertCount(10, $this->crawler->filter('td.pri_carer')); + } - // Check that we can see the centre labels in the page - $this->actingAs($centreUser, 'store') - ->visit(URL::route('store.registration.index')) - ->see('Tatooine') - ->see('Dagobah') - ->see('Coruscant'); + public function testItRedirectsToLastPageWhenRequestedPageExceedsLastPage(): void + { + $centre = factory(Centre::class)->create(); + $user = $this->userInCentre($centre); + factory(Registration::class, 5)->create(['centre_id' => $centre->id]); - // Check that each user we have added has a secondary info field - $this->assertCount(9, $this->crawler->filter('div.secondary_info')); + $this->actingAs($user, 'store') + ->visit(URL::route('store.registration.index', ['page' => '99'])); + + $this->seePageIs(URL::route('store.registration.index', ['page' => '1'])); } + // ── Left families ───────────────────────────────────────────────────────── public function testItDoesNotShowLeftFamiliesByDefault(): void { $centre = factory(Centre::class)->create(); + $centreUser = $this->userInCentre($centre); - // Create a Centre User - $centreUser = factory(CentreUser::class)->create([ - "name" => "test user", - "email" => "testuser@example.com", - "password" => bcrypt('test_user_pass'), - ]); - $centreUser->centres()->attach($centre->id, ['homeCentre' => true]); - - // Create 10 random registrations, which should be the per-page pagination limit. - $registrations = factory(Registration::class, 10)->create([ - "centre_id" => $centre->id, - ]); - - // Find and "leave" the first registrations Family + $registrations = factory(Registration::class, 10)->create(['centre_id' => $centre->id]); $leavingFamily = $registrations->first()->family; $leavingFamily->leaving_on = Carbon::now(); $leavingFamily->leaving_reason = config('arc.leaving_reasons')[0]; @@ -357,29 +330,33 @@ public function testItDoesNotShowLeftFamiliesByDefault(): void $this->actingAs($centreUser, 'store') ->visit(URL::route('store.registration.index')) - ->dontSee($leavingFamily->carers->first()); + ->dontSee($leavingFamily->carers->first()->name); } + public function testItIncludesLeftFamiliesWhenFamiliesLeftIsRequested(): void + { + $centre = factory(Centre::class)->create(); + $centreUser = $this->userInCentre($centre); + $regs = factory(Registration::class, 2)->create(['centre_id' => $centre->id]); + + $regs->first()->family->update(['leaving_on' => Carbon::now()]); + + $this->actingAs($centreUser, 'store') + ->visit(URL::route('store.registration.index', ['families_left' => '1'])); + + $this->see(URL::route('store.registration.view', $regs->first())); + $this->see(URL::route('store.registration.edit', $regs->last())); + } + + // ── Awaiting Dusk ──────────────────────────────────────────────────────── public function testItShowsLeftFamilyRegistrationsAsDistinct(): void { $this->markTestSkipped('Waiting for Dusk'); $centre = factory(Centre::class)->create(); + $centreUser = $this->userInCentre($centre); - // Create a Centre User - $centreUser = factory(CentreUser::class)->create([ - "name" => "test user", - "email" => "testuser@example.com", - "password" => bcrypt('test_user_pass'), - ]); - $centreUser->centres()->attach($centre->id, ['homeCentre' => true]); - - // Create 10 random registrations, which should be the per-page pagination limit. - $registrations = factory(Registration::class, 10)->create([ - "centre_id" => $centre->id, - ]); - - // Find and "leave" the first registrations Family + $registrations = factory(Registration::class, 10)->create(['centre_id' => $centre->id]); $leavingFamily = $registrations->first()->family; $leavingFamily->leaving_on = Carbon::now(); $leavingFamily->leaving_reason = config('arc.leaving_reasons')[0]; @@ -393,26 +370,13 @@ public function testItShowsLeftFamilyRegistrationsAsDistinct(): void $this->assertCount(9, $this->crawler->filter('tr.active')); } - public function testItPreventsAccessToLeftFamilyRegistrations(): void { $this->markTestSkipped('Waiting for Dusk'); $centre = factory(Centre::class)->create(); + $centreUser = $this->userInCentre($centre); - // Create a CentreUser - $centreUser = factory(CentreUser::class)->create([ - "name" => "test user", - "email" => "testuser@example.com", - "password" => bcrypt('test_user_pass'), - ]); - $centreUser->centres()->attach($centre->id, ['homeCentre' => true]); - - // Create 10 random registrations, which should be the per-page pagination limit. - $registrations = factory(Registration::class, 10)->create([ - "centre_id" => $centre->id, - ]); - - // Find and "leave" the first registrations Family + $registrations = factory(Registration::class, 10)->create(['centre_id' => $centre->id]); $leavingFamily = $registrations->first()->family; $leavingFamily->leaving_on = Carbon::now(); $leavingFamily->leaving_reason = config('arc.leaving_reasons')[0]; @@ -422,35 +386,9 @@ public function testItPreventsAccessToLeftFamilyRegistrations(): void ->visit(URL::route('store.registration.index')) ->check('#families_left'); - // Check the number of enabled and disabled buttons. $this->assertCount(2, $this->crawler->filter('tr.inactive td.right.no-wrap div.disabled')); $this->assertCount(0, $this->crawler->filter('tr.inactive td.right.no-wrap div:not(.disabled)')); $this->assertCount(0, $this->crawler->filter('tr.active td.right.no-wrap div.disabled')); $this->assertCount(18, $this->crawler->filter('tr.active td.right.no-wrap div:not(.disabled)')); } - - - public function testAVouchersButtonIsPresent(): void - { - // Create a Centre - $centre = factory(Centre::class)->create(); - - // Create a CentreUser - $centreUser = factory(CentreUser::class)->create([ - "name" => "test user", - "email" => "testuser@example.com", - "password" => bcrypt('test_user_pass'), - ]); - $centreUser->centres()->attach($centre->id, ['homeCentre' => true]); - - // Create a random registration with our centre. - factory(Registration::class)->create([ - "centre_id" => $centre->id, - ]); - - // Find a vouchers button - $this->actingAs($centreUser, 'store') - ->visit(URL::route('store.registration.index')) - ->see('Vouchers'); - } } diff --git a/tests/MysqlStoreTestCase.php b/tests/MysqlStoreTestCase.php index 1b03f54cd..d651efea6 100644 --- a/tests/MysqlStoreTestCase.php +++ b/tests/MysqlStoreTestCase.php @@ -5,7 +5,7 @@ use Config; use Exception; use Illuminate\Foundation\Testing\DatabaseMigrations; -use Laravel\BrowserKitTesting\TestCase as BaseTestCase; +use Tests\StoreTestCase as BaseTestCase; class MysqlStoreTestCase extends BaseTestCase { diff --git a/tests/Unit/Models/RegistrationModelTest.php b/tests/Unit/Models/RegistrationModelTest.php index 5fd4e7981..5a809e896 100644 --- a/tests/Unit/Models/RegistrationModelTest.php +++ b/tests/Unit/Models/RegistrationModelTest.php @@ -2,10 +2,12 @@ namespace Tests\Unit\Models; +use App\Carer; use App\Centre; use App\Family; use App\Registration; use Carbon\Carbon; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -13,6 +15,32 @@ class RegistrationModelTest extends TestCase { use RefreshDatabase; + // ── Helpers ─────────────────────────────────────────────────────────────── + + /** + * Create a registration and force its primary carer to a known name. + * The primary carer is the one with the lowest id (MIN), which is the carer + * the Registration factory creates automatically. + */ + private function registrationWithCarerName(Centre $centre, string $name): Registration + { + $reg = factory(Registration::class)->create(['centre_id' => $centre->id]); + $reg->family->carers()->orderBy('id')->first()->update(['name' => $name]); + return $reg->fresh(); + } + + /** + * Build a base query scoped to a single centre with the primary carer join applied. + * Scope tests chain from this to keep per-test setup noise out of assertions. + */ + private function baseQuery(Centre $centre): Builder + { + return Registration::query() + ->withPrimaryCarer() + ->whereIn('registrations.centre_id', [$centre->id]); + } + + // ── Creation ────────────────────────────────────────────────────────────── public function testItCanBeCreated(): void { @@ -28,6 +56,7 @@ public function testItCanBeCreated(): void $this->assertTrue($registration->save()); } + // ── scopeWhereActiveFamily ──────────────────────────────────────────────── public function testItCanReturnRegistrationsOnlyForActiveFamilies(): void { @@ -50,4 +79,186 @@ public function testItCanReturnRegistrationsOnlyForActiveFamilies(): void // check there are only 3. $this->assertEquals(3, Registration::whereActiveFamily()->count()); } + + // ── scopeWithPrimaryCarer ───────────────────────────────────────────────── + + public function testWithPrimaryCarerJoinsCarerWithLowestId(): void + { + $centre = factory(Centre::class)->create(); + $reg = factory(Registration::class)->create(['centre_id' => $centre->id]); + + // The factory creates one carer. Capture it and add a second with a higher id. + $reg->family->carers()->orderBy('id')->first()->update(['name' => 'Primary Carer']); + Carer::create(['name' => 'Secondary Carer', 'family_id' => $reg->family_id]); + + $result = $this->baseQuery($centre) + ->select('registrations.*', 'carers.name as carer_name') + ->where('registrations.id', $reg->id) + ->first(); + + $this->assertEquals('Primary Carer', $result->carer_name); + } + + public function testWithPrimaryCarerReturnsOneRowPerRegistrationRegardlessOfCarerCount(): void + { + $centre = factory(Centre::class)->create(); + + // Each registration gets a second carer; without the MIN(id) grouping + // a naive join would double the row count. + $regs = factory(Registration::class, 3)->create(['centre_id' => $centre->id]); + foreach ($regs as $reg) { + Carer::create(['name' => 'Extra Carer', 'family_id' => $reg->family_id]); + } + + $this->assertEquals(3, $this->baseQuery($centre)->count()); + } + + public function testWithPrimaryCarerMakesCarerNameAvailableForFiltering(): void + { + $centre = factory(Centre::class)->create(); + $reg = $this->registrationWithCarerName($centre, 'Findable Name'); + + // filterByCarerName uses carers.name in its WHERE — this would throw if + // withPrimaryCarer had not established the join first. + $results = $this->baseQuery($centre) + ->filterByCarerName('Findable') + ->get(); + + $this->assertCount(1, $results); + $this->assertEquals($reg->id, $results->first()->id); + } + + // ── scopeOrderByCarerName ───────────────────────────────────────────────── + + public function testOrderByCarerNameSortsAscendingByDefault(): void + { + $centre = factory(Centre::class)->create(); + $charlie = $this->registrationWithCarerName($centre, 'Charlie'); + $alice = $this->registrationWithCarerName($centre, 'Alice'); + $bob = $this->registrationWithCarerName($centre, 'Bob'); + + $ids = $this->baseQuery($centre) + ->orderByCarerName() + ->pluck('registrations.id') + ->toArray(); + + $this->assertEquals([$alice->id, $bob->id, $charlie->id], $ids); + } + + public function testOrderByCarerNameSortsDescendingWhenPassedTrue(): void + { + $centre = factory(Centre::class)->create(); + $charlie = $this->registrationWithCarerName($centre, 'Charlie'); + $alice = $this->registrationWithCarerName($centre, 'Alice'); + $bob = $this->registrationWithCarerName($centre, 'Bob'); + + $ids = $this->baseQuery($centre) + ->orderByCarerName(descending: true) + ->pluck('registrations.id') + ->toArray(); + + $this->assertEquals([$charlie->id, $bob->id, $alice->id], $ids); + } + + public function testOrderByCarerNameIsCaseInsensitive(): void + { + $centre = factory(Centre::class)->create(); + $lower = $this->registrationWithCarerName($centre, 'bob'); + $upper = $this->registrationWithCarerName($centre, 'Alice'); + + $ids = $this->baseQuery($centre) + ->orderByCarerName() + ->pluck('registrations.id') + ->toArray(); + + $this->assertEquals([$upper->id, $lower->id], $ids); + } + + // ── scopeFilterByCarerName ──────────────────────────────────────────────── + + public function testFilterByCarerNameFiltersOutNonMatchingCarers(): void + { + $centre = factory(Centre::class)->create(); + $match = $this->registrationWithCarerName($centre, 'Alice Smith'); + $nomatch = $this->registrationWithCarerName($centre, 'Bob Jones'); + + $ids = $this->baseQuery($centre) + ->filterByCarerName('Smith') + ->pluck('registrations.id') + ->toArray(); + + $this->assertContains($match->id, $ids); + $this->assertNotContains($nomatch->id, $ids); + } + + public function testFilterByCarerNameIsCaseInsensitive(): void + { + $centre = factory(Centre::class)->create(); + $reg = $this->registrationWithCarerName($centre, 'Alice SMITH'); + + $results = $this->baseQuery($centre) + ->filterByCarerName('smith') + ->get(); + + $this->assertCount(1, $results); + $this->assertEquals($reg->id, $results->first()->id); + } + + public function testFilterByCarerNameRanksExactMatchFirst(): void + { + $centre = factory(Centre::class)->create(); + $this->registrationWithCarerName($centre, 'Smith Jones'); // rank 1 — prefix + $exact = $this->registrationWithCarerName($centre, 'Smith'); // rank 0 — exact + $this->registrationWithCarerName($centre, 'Alice Smith'); // rank 2 — suffix + + $ids = $this->baseQuery($centre) + ->filterByCarerName('Smith') + ->pluck('registrations.id') + ->toArray(); + + $this->assertEquals($exact->id, $ids[0]); + } + + public function testFilterByCarerNameRanksPrefixMatchBeforeWordBoundaryMatch(): void + { + $centre = factory(Centre::class)->create(); + $prefix = $this->registrationWithCarerName($centre, 'Smith Alice'); // LIKE 'Smith %' → rank 1 + $boundary = $this->registrationWithCarerName($centre, 'Alice Smith'); // LIKE '% Smith' → rank 2 + + $ids = $this->baseQuery($centre) + ->filterByCarerName('Smith') + ->pluck('registrations.id') + ->toArray(); + + $this->assertEquals($prefix->id, $ids[0]); + $this->assertEquals($boundary->id, $ids[1]); + } + + public function testFilterByCarerNameRanksWordBoundaryMatchBeforeInternalMatch(): void + { + $centre = factory(Centre::class)->create(); + $internal = $this->registrationWithCarerName($centre, 'Jones Smithfield'); // rank 3 — internal + $boundary = $this->registrationWithCarerName($centre, 'Alice Smith'); // rank 2 — suffix + + $ids = $this->baseQuery($centre) + ->filterByCarerName('Smith') + ->pluck('registrations.id') + ->toArray(); + + $this->assertEquals($boundary->id, $ids[0]); + $this->assertEquals($internal->id, $ids[1]); + } + + public function testFilterByCarerNameReturnsNoResultsWhenNothingMatches(): void + { + $centre = factory(Centre::class)->create(); + $this->registrationWithCarerName($centre, 'Alice Jones'); + $this->registrationWithCarerName($centre, 'Bob Williams'); + + $results = $this->baseQuery($centre) + ->filterByCarerName('XyzNoMatch') + ->get(); + + $this->assertCount(0, $results); + } } From 0fbd4849d247e60e2d0d2aaa1a65e100fff0c318 Mon Sep 17 00:00:00 2001 From: charles strange Date: Sun, 19 Apr 2026 00:36:27 +0100 Subject: [PATCH 096/168] fix: allocate js isn't injected in the right place --- .../views/store/voucher-manager/allocate-vouchers.blade.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/resources/views/store/voucher-manager/allocate-vouchers.blade.php b/resources/views/store/voucher-manager/allocate-vouchers.blade.php index 47227daa4..36a68c6b3 100644 --- a/resources/views/store/voucher-manager/allocate-vouchers.blade.php +++ b/resources/views/store/voucher-manager/allocate-vouchers.blade.php @@ -94,10 +94,9 @@ class="delete-button" -@pushonce('scripts') +@pushonce('js') +@endpushonce + From 300a135a63aacafe9b884da1b4c51c58c081e797 Mon Sep 17 00:00:00 2001 From: charles strange Date: Sun, 19 Apr 2026 00:43:24 +0100 Subject: [PATCH 099/168] feature: modify bundlecontroller to cope with numeric vouchers --- .../Controllers/Store/BundleController.php | 37 +++++++++--- .../Requests/StoreAppendBundleRequest.php | 57 +++++++++++-------- app/Voucher.php | 28 ++++++++- 3 files changed, 91 insertions(+), 31 deletions(-) diff --git a/app/Http/Controllers/Store/BundleController.php b/app/Http/Controllers/Store/BundleController.php index da741beb8..e06eaff88 100644 --- a/app/Http/Controllers/Store/BundleController.php +++ b/app/Http/Controllers/Store/BundleController.php @@ -19,6 +19,7 @@ use Illuminate\Support\Facades\Log; use Illuminate\Support\HtmlString; use Illuminate\Support\Str; +use Request; use Throwable; class BundleController extends Controller @@ -59,20 +60,35 @@ public function create(Registration $registration): View } /** - * Append a single voucher or range of vouchers to the current bundle. + * Append a single voucher, range of vouchers, or a quantity drawn from the pool + * to the current bundle. */ public function addVouchersToCurrentBundle( StoreAppendBundleRequest $request, Registration $registration, ): RedirectResponse { - $voucherCodes = Voucher::generateCodeRange( - $request->input('start'), - $request->input('end'), - ); - $managerRoute = $this->managerRoute($registration); - $errors = count($voucherCodes) <= config('arc.bundle_max_voucher_append') + if ($request->filled('quantity')) { + try { + $voucherCodes = Voucher::claimFromPool((int) $request->input('quantity')) + ->pluck('code') + ->all(); + } catch (Throwable $e) { + return $this->redirectAfterRequest( + ['pool' => $e->getMessage()], + $managerRoute, + $managerRoute, + ); + } + } else { + $voucherCodes = Voucher::generateCodeRange( + $request->input('start'), + $request->input('end'), + ); + } + + $errors = (count($voucherCodes) <= config('arc.bundle_max_voucher_append')) ? $registration->currentBundle()->addVouchers($voucherCodes) : ['append' => count($voucherCodes)]; @@ -290,4 +306,11 @@ private function buildSuccessMessage(Bundle $bundle): string $name, ); } + + /** + * + */ + public function requestPayment(Request $request) { + // + } } diff --git a/app/Http/Requests/StoreAppendBundleRequest.php b/app/Http/Requests/StoreAppendBundleRequest.php index 3d45a7217..d238be953 100644 --- a/app/Http/Requests/StoreAppendBundleRequest.php +++ b/app/Http/Requests/StoreAppendBundleRequest.php @@ -8,11 +8,9 @@ class StoreAppendBundleRequest extends FormRequest { /** - * Determine if the user is authorized to make this request. - * - * @return bool + * Determine if the user is authorized to make this request */ - public function authorize() + public function authorize(): true { // TODO : determine of existing registration route protection is sufficient. return true; @@ -20,38 +18,51 @@ public function authorize() /** * Get the validation rules that apply to the request. - * - * @return array */ - public function rules() + public function rules(): array { /* * These rules validate that the form data is well-formed. * It is NOT responsible for the context validation of that data. */ - $rules = [ - // MUST be present, not null and string - 'start' => 'required|string|exists:vouchers,code', - // MAY be present, nullable, string, code exists, is GT start and same sponsor as start - 'end' => 'nullable|string|exists:vouchers,code|codeGreaterThan:start|sameSponsor:start', + return [ + 'voucher-quantity' => [ + 'nullable', + 'integer', + 'between:1,' . config('arc.bundle_max_voucher_append'), + 'required_without:start', + ], + 'start' => [ + 'exclude_if:voucher-quantity,present', + 'required_without:voucher-quantity', + 'string', + 'exists:vouchers,code', + ], + 'end' => [ + 'exclude_if:voucher-quantity,present', + 'nullable', + 'string', + 'exists:vouchers,code', + 'codeGreaterThan:start', + 'sameSponsor:start', + ], ]; - - return $rules; } - protected function prepareForValidation() + protected function prepareForValidation(): void { - // get the input and remove null/empty values. - $input = array_filter( - $this->all(['start', 'end']), - 'strlen' - ); + if ($this->filled('voucher-quantity')) { + $this->replace(['voucher-quantity' => (int)$this->input('voucher-quantity')]); + return; + } + + $input = array_filter($this->all(['start', 'end']), 'strlen'); foreach ($input as $key => $value) { $clean = Voucher::cleanCodes((array)$value); - $input[$key] = strtoupper((array_shift($clean))); + $input[$key] = strtoupper(array_shift($clean)); } - // replace old input with new input + $this->replace($input); } -} \ No newline at end of file +} diff --git a/app/Voucher.php b/app/Voucher.php index d2bea94f4..5044939d8 100644 --- a/app/Voucher.php +++ b/app/Voucher.php @@ -14,6 +14,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Collection; use Log; +use RuntimeException; use Throwable; /** @@ -313,7 +314,7 @@ public static function getDeliverableVoucherRangesByShortCode(string $shortcode) LEFT JOIN vouchers as v2 ON final_id = v2.id - + ORDER BY t1.start " ); @@ -587,4 +588,29 @@ public function deepExport(bool $includeVoucherStates = false): array } return $v; } + + /** + * Claim the first $quantity available (printed, unallocated) vouchers from the pool. + * Uses a pessimistic lock to prevent concurrent allocation of the same vouchers. + * @throws Throwable + */ + public static function claimFromPool(int $quantity): Collection + { + return DB::transaction(static function () use ($quantity) { + $vouchers = self::where('currentstate', 'printed') + ->whereNull('bundle_id') + ->orderBy('id') + ->limit($quantity) + ->lockForUpdate() + ->get(); + + if ($vouchers->count() < $quantity) { + throw new RuntimeException( + "Pool has {$vouchers->count()} vouchers available, {$quantity} requested." + ); + } + + return $vouchers; + }); + } } From b1678dc4c64fb74d64df653cb7fc16e095797ab6 Mon Sep 17 00:00:00 2001 From: charles strange Date: Sun, 19 Apr 2026 00:44:27 +0100 Subject: [PATCH 100/168] feature: add gates and split blade compliation on that --- app/Providers/AppServiceProvider.php | 16 ++++ .../views/store/manage_vouchers.blade.php | 10 +- .../store/voucher-manager/collect.blade.php | 92 +++++++++++++++++++ routes/store.php | 8 +- 4 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 resources/views/store/voucher-manager/collect.blade.php diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index e31e7738e..a98e9479e 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use App\CentreUser; use App\Services\EnvWriter; use App\View\Composers\PaymentsComposer; use Illuminate\Pagination\Paginator; @@ -39,6 +40,21 @@ public function boot(): void // permit if the application is debugging return config('app.debug') === true; }); + + Gate::define('collect-vouchers', static function (CentreUser $centreUser) { + $centreId = session('CentreUserCurrentCentreId'); + + if (! $centreId) { + return false; + } + + // is this user permitted to work on this can_collect centre? + return $centreUser + ->centres() + ->where('centres.id', $centreId) + ->where('centres.can_collect', true) + ->exists(); + }); } /** diff --git a/resources/views/store/manage_vouchers.blade.php b/resources/views/store/manage_vouchers.blade.php index 9b4e97c65..e4d077625 100644 --- a/resources/views/store/manage_vouchers.blade.php +++ b/resources/views/store/manage_vouchers.blade.php @@ -9,8 +9,14 @@
@include('store.voucher-manager.family-info') @include('store.voucher-manager.collection-history') - @include('store.voucher-manager.allocate-vouchers') - @include('store.voucher-manager.pickup') + + @can('collect-vouchers') + @include('store.voucher-manager.allocate-vouchers-numeric') + @include('store.voucher-manager.collect') + @else + @include('store.voucher-manager.allocate-vouchers') + @include('store.voucher-manager.pickup') + @endcan
@endsection diff --git a/resources/views/store/voucher-manager/collect.blade.php b/resources/views/store/voucher-manager/collect.blade.php new file mode 100644 index 000000000..bacb5c408 --- /dev/null +++ b/resources/views/store/voucher-manager/collect.blade.php @@ -0,0 +1,92 @@ +{{-- Requires: $programme, $registration, $vouchers_amount, $carers, $centre --}} +
+
+ +

Request Payment

+
+ +
+

There's {{ $vouchers_amount }} @choice('{1}voucher|[0,2,*]vouchers', $vouchers_amount) waiting for this {{ $programme === 0 ? 'family' : 'household' }}

+
+ +
+ @method('PUT') + @csrf +
+
+ +
+ + +
+
+ +
+ +
+ + + +
+
+ +
+ +
+ + +
+
+ + +
+
+ + + Change allocated vouchers + +
+ +@pushonce('scripts') + +@endpushonce diff --git a/routes/store.php b/routes/store.php index d23944270..1113deaf1 100644 --- a/routes/store.php +++ b/routes/store.php @@ -38,7 +38,7 @@ })->name('store.base'); // Authenticated routes -Route::middleware('auth:store')->group(function () { +Route::middleware('auth:store')->group(function (): void { Route::post('logout', [LoginController::class, 'logout']) ->name('store.logout'); @@ -118,6 +118,12 @@ Route::post('/registrations/{registration}/vouchers', [BundleController::class, 'addVouchersToCurrentBundle']) ->name('store.registration.vouchers.post') ->whereNumber('registration'); + + Route::post('/registrations/{registration}/vouchers/payment-requests', + [BundleController::class, 'requestPayment'] + ) + ->name('store.registration.vouchers.payment-requests.post') + ->whereNumber('registration'); }); // Export routes (requires export policy on CentreUser) From f5e56e272b80131b477da7076701308439cb3cbd Mon Sep 17 00:00:00 2001 From: charles strange Date: Sun, 19 Apr 2026 00:45:15 +0100 Subject: [PATCH 101/168] feature: rewrite and modernise tests for bundlecontroller, add voucher-quantity tests --- .../Store/BundleControllerTest.php | 904 ++++++++---------- 1 file changed, 389 insertions(+), 515 deletions(-) diff --git a/tests/Unit/Controllers/Store/BundleControllerTest.php b/tests/Unit/Controllers/Store/BundleControllerTest.php index c411cd87d..af49957de 100644 --- a/tests/Unit/Controllers/Store/BundleControllerTest.php +++ b/tests/Unit/Controllers/Store/BundleControllerTest.php @@ -2,60 +2,54 @@ namespace Tests\Unit\Controllers\Store; -use App\Registration; use App\Bundle; use App\Centre; use App\CentreUser; use App\Family; +use App\Registration; use App\Sponsor; use App\Voucher; -use Auth; use Carbon\Carbon; use Illuminate\Foundation\Testing\RefreshDatabase; -use Session; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Session; +use PHPUnit\Framework\Attributes\DataProvider; use Tests\StoreTestCase; class BundleControllerTest extends StoreTestCase { use RefreshDatabase; - protected $centre; - protected $centreUser; - protected $testCodes; + protected Centre $centre; + protected CentreUser $centreUser; protected Registration $registration; - protected $bundle; + protected array $testCodes; + protected string $programme; protected function setUp(): void { parent::setUp(); + $this->centre = factory(Centre::class)->create(); - // Create a User $this->centreUser = factory(CentreUser::class)->create([ - "name" => "test user", - "email" => "testuser@example.com", - "password" => bcrypt('test_user_pass'), + 'name' => 'test user', + 'email' => 'testuser@example.com', + 'password' => bcrypt('test_user_pass'), ]); $this->centreUser->centres()->attach($this->centre->id, ['homeCentre' => true]); - // A Registration on that centre $this->registration = factory(Registration::class)->create([ - 'centre_id' => $this->centre->id + 'centre_id' => $this->centre->id, ]); - // Make some vouchers - $this->testCodes = [ - 'TST09999', - 'TST10000', - 'TST10001' - ]; + $this->testCodes = ['TST09999', 'TST10000', 'TST10001']; + // Auth context is required by the voucher state-machine transition logger. Auth::login($this->centreUser); - foreach ($this->testCodes as $testCode) { - $voucher = factory(Voucher::class)->state('printed')->create([ - 'code' => $testCode - ]); + foreach ($this->testCodes as $code) { + $voucher = factory(Voucher::class)->state('printed')->create(['code' => $code]); $voucher->applyTransition('dispatch'); } @@ -64,658 +58,538 @@ protected function setUp(): void Auth::logout(); } + // ------------------------------------------------------------------------- + // Append-vouchers validation — data provider + // ------------------------------------------------------------------------- - public function testICannotSubmitInvalidValuesToAppendVouchers(): void + /** + * @return array, string, string}> + * + * Config placeholders: config() is unavailable in static providers (they are constructed + * before the Laravel application boots). Two placeholders are used instead and resolved + * inside the test method once the container is live: + * :max: → config('arc.bundle_max_voucher_append') + * :max_plus_one: → config('arc.bundle_max_voucher_append') + 1 + */ + public static function appendVoucherValidationCases(): array { - $dataSets = [ - // no data - [ - "data" => [], - "outcome" => ["start" => "The start field is required."] + return [ + // --- start field (voucher-quantity absent branch) --- + 'start: absent with no data submitted' => [ + [], + 'start', + 'The start field is required when voucher-quantity is not present.', ], - // start is not present - [ - "data" => ['end' => 'tst10001'], - "outcome" => ["start" => "The start field is required."] + 'start: absent when only end supplied' => [ + ['end' => 'tst10001'], + 'start', + 'The start field is required when voucher-quantity is not present.', ], - // start is present but null - [ - "data" => ["start" => '', 'end' => 'tst10001'], - "outcome" => ["start" => "The start field is required."] + 'start: empty string stripped by prepareForValidation' => [ + ['start' => '', 'end' => 'tst10001'], + 'start', + 'The start field is required when voucher-quantity is not present.', ], - // start is not a valid voucher code - [ - "data" => ["start" => 'invalidVoucher', 'end' => 'tst10001' ], - "outcome" => ["start" => "The selected start is invalid."] + 'start: value not found in vouchers table' => [ + ['start' => 'invalidVoucher', 'end' => 'tst10001'], + 'start', + 'The selected start is invalid.', ], - // end is not a valid voucher code - [ - "data" => ["start" => 'tst09999', 'end' => 'invalidCode' ], - "outcome" => ["end" => "The selected end is invalid."] + + // --- end field --- + 'end: value not found in vouchers table' => [ + ['start' => 'tst09999', 'end' => 'invalidCode'], + 'end', + 'The selected end is invalid.', + ], + 'end: different sponsor prefix to start' => [ + ['start' => 'tst09999', 'end' => 'txt10000'], + 'end', + 'The end field must be the same sponsor as the start field.', ], - // end is not the same shortcode as start - [ - "data" => ["start" => 'tst09999', 'end' => 'txt10000' ], - "outcome" => ["end" => "The end field must be the same sponsor as the start field."] + 'end: lower sequence number than start' => [ + ['start' => 'tst10001', 'end' => 'tst09999'], + 'end', + 'The end field must be greater than the start field.', + ], + + // --- voucher-quantity field (early-return branch) --- + 'voucher-quantity: zero is below the allowed minimum of 1' => [ + ['voucher-quantity' => '0'], + 'voucher-quantity', + 'The voucher-quantity must be between 1 and :max:.', ], - // end is not higher than start - [ - "data" => ["start" => 'tst10001', 'end' => 'tst09999' ], - "outcome" => ["end" => "The end field must be greater than the start field."] + 'voucher-quantity: negative value is below the allowed minimum of 1' => [ + ['voucher-quantity' => '-1'], + 'voucher-quantity', + 'The voucher-quantity must be between 1 and :max:.', + ], + 'voucher-quantity: exceeds the configured maximum' => [ + ['voucher-quantity' => ':max_plus_one:'], + 'voucher-quantity', + 'The voucher-quantity must be between 1 and :max:.', + ], + 'voucher-quantity: required when start also absent' => [ + [], + 'voucher-quantity', + 'The voucher-quantity field is required when start is not present.', ], ]; + } - $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); - $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); - - foreach ($dataSets as $set) { - $response = $this->actingAs($this->centreUser, 'store') - ->visit($route) - ->post( - $post_route, - $set["data"] - ) - ; - // work out which field we're testing. - $field = array_keys($set['outcome'])[0]; - - // Dig out errors from Session - $all = session()->all(); - self::assertArrayHasKey("errors", $all); - $errors = session("errors")->get($field); - - // Check our specific message is present - $this->assertContains($set['outcome'][$field], $errors); - - // we follow that to the correct page; - $this->followRedirects() - ->seePageIs($route) - ->assertResponseStatus(200) - ; - } + #[DataProvider('appendVoucherValidationCases')] + public function testICannotSubmitInvalidValuesToAppendVouchers( + array $data, + string $field, + string $expectedMessage, + ): void { + $max = (string)config('arc.bundle_max_voucher_append'); + + // Resolve placeholders that cannot be evaluated inside a static data provider. + $data = array_map(static function (string $v) use ($max) { + return str_replace(':max_plus_one:', (string)((int)$max + 1), $v); + }, $data); + $expectedMessage = str_replace(':max:', $max, $expectedMessage); + + $route = route('store.registration.voucher-manager', ['registration' => $this->registration->id]); + $postRoute = route('store.registration.vouchers.post', ['registration' => $this->registration->id]); + + $this->actingAs($this->centreUser, 'store') + ->visit($route) + ->post($postRoute, $data); + + $errors = session('errors')->get($field); + + $this->assertNotEmpty($errors, "Expected validation errors for field '$field'"); + $this->assertContains($expectedMessage, $errors); + + $this->followRedirects() + ->seePageIs($route) + ->assertResponseStatus(200); } + // ------------------------------------------------------------------------- + // Disbursement validation — data provider + // ------------------------------------------------------------------------- - public function testIMustDisburseWithAllRelevantFields(): void + /** + * @return array, string, string}> + */ + public static function disburseValidationCases(): array { - $dataSets = [ - [ - "data" => [ - "collected_at" => "1", "collected_on" => "2018-07-21" - ], - "outcome" => [ "collected_by" => "The collected by field is required when collected at / collected on is present."], + return [ + 'collected_by missing' => [ + ['collected_at' => '1', 'collected_on' => '2018-07-21'], + 'collected_by', + 'The collected by field is required when collected at / collected on is present.', ], - [ - "data" => [ - "collected_by" => "1", "collected_on" => "2018-07-21" - ], - "outcome" => [ "collected_at" => "The collected at field is required when collected on / collected by is present."], + 'collected_at missing' => [ + ['collected_by' => '1', 'collected_on' => '2018-07-21'], + 'collected_at', + 'The collected at field is required when collected on / collected by is present.', ], - [ - "data" => [ - "collected_at" => "1", "collected_by" => "1" - ], - "outcome" => [ "collected_on" => "The collected on field is required when collected at / collected by is present."], + 'collected_on missing' => [ + ['collected_at' => '1', 'collected_by' => '1'], + 'collected_on', + 'The collected on field is required when collected at / collected by is present.', ], - [ - "data" => [ - "collected_at" => "1", "collected_on" => "invalid", "collected_by" => "1" - ], - "outcome" => [ "collected_on" => "The collected on does not match the format Y-m-d."], + 'collected_on wrong date format' => [ + ['collected_at' => '1', 'collected_on' => 'invalid', 'collected_by' => '1'], + 'collected_on', + 'The collected on does not match the format Y-m-d.', ], - [ - "data" => [ - "collected_at" => "9999", "collected_on" => "2018-07-21", "collected_by" => "1" - ], - "outcome" => [ "collected_at" => "The selected collected at is invalid."], + 'collected_at centre does not exist' => [ + ['collected_at' => '9999', 'collected_on' => '2018-07-21', 'collected_by' => '1'], + 'collected_at', + 'The selected collected at is invalid.', ], - [ - "data" => [ - "collected_at" => "1", "collected_on" => "2018-07-21", "collected_by" => "9999" - ], - "outcome" => [ "collected_by" => "The selected collected by is invalid."], + 'collected_by carer does not exist' => [ + ['collected_at' => '1', 'collected_on' => '2018-07-21', 'collected_by' => '9999'], + 'collected_by', + 'The selected collected by is invalid.', ], ]; + } - $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); - $put_route = route('store.registration.vouchers.put', ['registration' => $this->registration->id]); - - foreach ($dataSets as $set) { - $response = $this->actingAs($this->centreUser, 'store') - ->visit($route) - ->put( - $put_route, - $set["data"] - ) - ; - // work out which field we're testing. - $field = array_keys($set['outcome'])[0]; - - // Dig out errors from Session - $response->seeInSession('errors'); - $errors = Session::get("errors")->get($field); - - // Check our specific message is present - $this->assertContains($set['outcome'][$field], $errors); - - // we follow that to the correct page; - $this->followRedirects() - ->seePageIs($route) - ->assertResponseStatus(200) - ; - } + #[DataProvider('disburseValidationCases')] + public function testIMustDisburseWithAllRelevantFields( + array $data, + string $field, + string $expectedMessage, + ): void { + $route = route('store.registration.voucher-manager', ['registration' => $this->registration->id]); + $putRoute = route('store.registration.vouchers.put', ['registration' => $this->registration->id]); + + $this->actingAs($this->centreUser, 'store') + ->visit($route) + ->put($putRoute, $data); + + $errors = session('errors')->get($field); + + $this->assertNotEmpty($errors, "Expected validation errors for field '$field'"); + $this->assertContains($expectedMessage, $errors); + + $this->followRedirects() + ->seePageIs($route) + ->assertResponseStatus(200); } + // ------------------------------------------------------------------------- + // Adding vouchers to a bundle + // ------------------------------------------------------------------------- - public function testICanAddManyVouchers(): void + public function testICanAddSingleVouchers(): void { - $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); - $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); + $route = route('store.registration.voucher-manager', ['registration' => $this->registration->id]); + $postRoute = route('store.registration.vouchers.post', ['registration' => $this->registration->id]); - // Add many vouchers; $this->actingAs($this->centreUser, 'store') - ->post( - $post_route, - [ - 'start' => $this->testCodes[0], - 'end' => $this->testCodes[count($this->testCodes) - 1] - ] - ); + ->post($postRoute, ['start' => $this->testCodes[0]]); $this->followRedirects() ->seePageIs($route) - ->assertResponseStatus(200) - ; - /** @var Bundle $currentBundle */ - // Get our currentBundle - $currentBundle = $this->registration->currentBundle(); + ->assertResponseStatus(200); - // See that it's got many vouchers. - $this->assertEquals(count($this->testCodes), $currentBundle->vouchers()->count()); + $this->assertSame(1, $this->registration->currentBundle()->vouchers()->count()); } + public function testICanAddManyVouchers(): void + { + $route = route('store.registration.voucher-manager', ['registration' => $this->registration->id]); + $postRoute = route('store.registration.vouchers.post', ['registration' => $this->registration->id]); + + $this->actingAs($this->centreUser, 'store') + ->post($postRoute, [ + 'start' => $this->testCodes[0], + 'end' => $this->testCodes[count($this->testCodes) - 1], + ]); + + $this->followRedirects() + ->seePageIs($route) + ->assertResponseStatus(200); + + $this->assertSame(count($this->testCodes), $this->registration->currentBundle()->vouchers()->count()); + } public function testICannotAddTooManyVouchersToABundle(): void { - $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); - $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); + $route = route('store.registration.voucher-manager', ['registration' => $this->registration->id]); + $postRoute = route('store.registration.vouchers.post', ['registration' => $this->registration->id]); - // Get the maxAdd value currently 101; $overMaxAdd = config('arc.bundle_max_voucher_append') + 1; - - // Make the range 1-101, - $startCode = "BIG00001"; - $endCode = "BIG" . str_pad($overMaxAdd, 5, "0", STR_PAD_LEFT); + $startCode = 'BIG00001'; + $endCode = 'BIG' . str_pad((string)$overMaxAdd, 5, '0', STR_PAD_LEFT); $bigRange = Voucher::generateCodeRange($startCode, $endCode); + $this->assertCount($overMaxAdd, $bigRange); - // Create the vouchers for the range; Auth::login($this->centreUser); - foreach ($bigRange as $testCode) { - $voucher = factory(Voucher::class)->state('printed')->create([ - 'code' => $testCode - ]); + foreach ($bigRange as $code) { + $voucher = factory(Voucher::class)->state('printed')->create(['code' => $code]); $voucher->applyTransition('dispatch'); } Auth::logout(); - // Attempt to bind the vouchers to the bundle $response = $this->actingAs($this->centreUser, 'store') - ->post( - $post_route, - [ - 'start' => $startCode, - 'end' => $endCode, - ] - ); + ->post($postRoute, ['start' => $startCode, 'end' => $endCode]); - // Confirm that we have supplied the appropriate error message to the session $response->seeInSession('error_messages'); $this->assertTrue($this->hasMatchingErrorMessage( Session::get('error_messages'), - '/Failed adding more than ' . config('arc.bundle_max_voucher_append') . ' vouchers/' + '/Failed adding more than ' . config('arc.bundle_max_voucher_append') . ' vouchers/', )); - // see we're redirected back $this->followRedirects() ->seePageIs($route) - ->assertResponseStatus(200) - ; - - // See we have no vouchers added. - /** @var Bundle $currentBundle */ - // Get our currentBundle - $currentBundle = $this->registration->currentBundle(); + ->assertResponseStatus(200); - // See that it's got no vouchers. - $this->assertEquals(0, $currentBundle->vouchers()->count()); + $this->assertSame(0, $this->registration->currentBundle()->vouchers()->count()); } - - public function testICanAddSingleVouchers(): void + public function testICannotAddAVoucherAllocatedInACentreIHaveAccessTo(): void { - $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); - $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); + $route = route('store.registration.voucher-manager', ['registration' => $this->registration->id]); + $postRoute = route('store.registration.vouchers.post', ['registration' => $this->registration->id]); - // Add a voucher; + // Allocate the first test voucher to the primary registration. $this->actingAs($this->centreUser, 'store') - ->post($post_route, ['start' => $this->testCodes[0]]); + ->post($postRoute, ['start' => $this->testCodes[0]]); - $this->followRedirects() - ->seePageIs($route) - ->assertResponseStatus(200) - ; + // Create a second registration in the same centre. + $registrationTwo = factory(Registration::class)->create(['centre_id' => $this->centre->id]); - /** @var Bundle $currentBundle */ - // Get our currentBundle - $currentBundle = $this->registration->currentBundle(); + $route2 = route('store.registration.voucher-manager', ['registration' => $registrationTwo->id]); + $postRoute2 = route('store.registration.vouchers.post', ['registration' => $registrationTwo->id]); - // See that it's got one voucher. - $this->assertEquals(1, $currentBundle->vouchers()->count()); - } + // Attempt to add the already-allocated voucher to the second bundle. + $response = $this->actingAs($this->centreUser, 'store') + ->visit($route2) + ->post($postRoute2, ['start' => $this->testCodes[0]]); + $this->assertSame(0, $registrationTwo->currentBundle()->vouchers()->count()); + $response->seeInSession('error_messages'); - public function testICanDeleteTheCurrentBundle(): void + $entity = Family::getAlias($this->programme); + $expectedPattern = '~These vouchers are currently allocated to a different ' . $entity + . '. Click on the voucher number to view the other ' . $entity + . '\'s record: ' . $this->testCodes[0] . '~'; + + $this->assertTrue($this->hasMatchingErrorMessage(Session::get('error_messages'), $expectedPattern)); + + $this->followRedirects() + ->seeInElement( + 'div[class="alert-message error"]', + 'Click on the voucher number to view the other ' . $entity + . '\'s record: ' . $this->testCodes[0] . '', + ); + } + + public function testICannotAddAVoucherAllocatedInACentreIDoNotHaveAccessTo(): void { - /** @var Bundle $currentBundle */ - $currentBundle = $this->registration->currentBundle(); + $postRoute = route('store.registration.vouchers.post', ['registration' => $this->registration->id]); - Auth::login($this->centreUser); - // Make some vouchers to bundle. - $testCodes = [ - 'TST0123455', - 'TST0123456', - 'TST0123457' - ]; - foreach ($testCodes as $testCode) { - $voucher = factory(Voucher::class)->state('printed')->create([ - 'code' => $testCode - ]); - $voucher->applyTransition('dispatch'); - $voucher->bundle()->associate($currentBundle)->save(); - } + // Allocate the second test voucher to the primary registration. + $this->actingAs($this->centreUser, 'store') + ->post($postRoute, ['start' => $this->testCodes[1]]); + + // Build a completely separate sponsor / centre / user / registration hierarchy. + $sponsor2 = factory(Sponsor::class)->create(); + $centre2 = factory(Centre::class)->create(['sponsor_id' => $sponsor2->id]); + $centreUser2 = factory(CentreUser::class)->create([ + 'name' => 'second test user', + 'email' => 'testuser2@example.com', + 'password' => bcrypt('test_user_pass2'), + 'role' => 'centre_user', + ]); + $centreUser2->centres()->attach($centre2->id, ['homeCentre' => true]); - // there should be 3 vouchers! - $this->assertEquals(count($testCodes), $currentBundle->vouchers()->count()); + $registrationTwo = factory(Registration::class)->create(['centre_id' => $centre2->id]); - // Stash vouchers for test later - //$vouchers = $currentBundle->vouchers()->get(); + $route2 = route('store.registration.voucher-manager', ['registration' => $registrationTwo->id]); + $postRoute2 = route('store.registration.vouchers.post', ['registration' => $registrationTwo->id]); - $delete_route = route( - 'store.registration.vouchers.delete', - [ - 'registration' => $this->registration->id, - ] - ); + $response = $this->actingAs($centreUser2, 'store') + ->visit($route2) + ->post($postRoute2, ['start' => $this->testCodes[1]]); - // Hit the route with a delete request; - $this->actingAs($this->centreUser, 'store') - ->delete($delete_route) - ; + $this->assertSame(0, $registrationTwo->currentBundle()->vouchers()->count()); - // refresh bundle - $currentBundle->refresh(); - // See less vouchers - $this->assertEquals(0, $currentBundle->vouchers()->count()); + $response->seeInSession('error_messages'); + + $entity = Family::getAlias($this->programme); + $expectedPattern = '~These vouchers are allocated to a different ' . $entity + . ' in a centre you can\'t access: ' . $this->testCodes[1] . '~'; - //Check all vouchers have NULL bundle_id + $this->assertTrue($this->hasMatchingErrorMessage(Session::get('error_messages'), $expectedPattern)); - $vouchers = Voucher::whereIn('code', $testCodes)->get(); - foreach ($vouchers as $v) { - $this->assertNull($v->bundle_id); - } + $this->followRedirects() + ->seeInElement( + 'div[class="alert-message error"]', + 'These vouchers are allocated to a different ' . $entity + . ' in a centre you can\'t access: ' . $this->testCodes[1], + ); } + // ------------------------------------------------------------------------- + // Input cleaning + // ------------------------------------------------------------------------- - public function testICanDeleteANamedVoucher(): void + public function testItCanAcceptAndCleanVouchersWithSpacesIn(): void { - /** @var Bundle $currentBundle */ - $currentBundle = $this->registration->currentBundle(); + $route = route('store.registration.voucher-manager', ['registration' => $this->registration->id]); + $postRoute = route('store.registration.vouchers.post', ['registration' => $this->registration->id]); - Auth::login($this->centreUser); - // Make some vouchers to bundle. - $testCodes = [ - 'TST0123455', - 'TST0123456', - 'TST0123457' - ]; - foreach ($testCodes as $testCode) { - $voucher = factory(Voucher::class)->state('printed')->create([ - 'code' => $testCode - ]); - $voucher->applyTransition('dispatch'); - $voucher->bundle()->associate($currentBundle)->save(); - } + // Insert a space at a random position in the voucher code. + $voucherCode = $this->testCodes[0]; + $voucherCode = substr_replace($voucherCode, ' ', random_int(0, strlen($voucherCode)), 0); - // there should be 3 vouchers! - $this->assertEquals(count($testCodes), $currentBundle->vouchers()->count()); + $this->actingAs($this->centreUser, 'store') + ->post($postRoute, ['start' => $voucherCode]); - // find the first voucher - $voucher = $currentBundle->vouchers()->first(); + $this->followRedirects() + ->seePageIs($route) + ->assertResponseStatus(200); - $delete_route = route( - 'store.registration.voucher.delete', - [ - 'registration' => $this->registration->id, - 'voucher' => $voucher->id - ] - ); + $this->assertSame(1, $this->registration->currentBundle()->vouchers()->count()); + } + + public function testItHasSparseFormDataCleanedBeforeProcessing(): void + { + $postRoute = route('store.registration.vouchers.post', ['registration' => $this->registration->id]); - // Hit the route with a delete request; + // A null end should be treated as a single-code add. $this->actingAs($this->centreUser, 'store') - ->delete($delete_route) - ; + ->post($postRoute, ['start' => $this->testCodes[0], 'end' => null]); - // refresh bundle - $currentBundle->refresh(); - // See less vouchers - $this->assertEquals(count($testCodes) - 1, $currentBundle->vouchers()->count()); + $lastVoucher = $this->registration->currentBundle()->vouchers()->orderByDesc('id')->first(); + $this->assertSame($this->testCodes[0], $lastVoucher->code); - // Refresh the detached voucher - $voucher->refresh(); - // Assert voucher is unbundled - $this->assertNull($voucher->bundle_id); + // A blank-string end should also be treated as a single-code add. + $this->actingAs($this->centreUser, 'store') + ->post($postRoute, ['start' => $this->testCodes[1], 'end' => '']); - // Assert voucher is back to dispatched - $this->assertEquals('dispatched', $voucher->currentstate); + $lastVoucher = $this->registration->currentBundle()->vouchers()->orderByDesc('id')->first(); + $this->assertSame($this->testCodes[1], $lastVoucher->code); } + // ------------------------------------------------------------------------- + // Sync (PUT) + // ------------------------------------------------------------------------- public function testICanSyncAnArrayOfVouchers(): void { - $put_route = route('store.registration.vouchers.put', ['registration' => $this->registration->id]); + $putRoute = route('store.registration.vouchers.put', ['registration' => $this->registration->id]); - // sync a voucher; + // Sync a single voucher. $this->actingAs($this->centreUser, 'store') - ->put($put_route, ['vouchers' => [$this->testCodes[0]]]); + ->put($putRoute, ['vouchers' => [$this->testCodes[0]]]); $currentBundle = $this->registration->currentBundle(); - $this->assertEquals(1, $currentBundle->vouchers()->count()); + $this->assertSame(1, $currentBundle->vouchers()->count()); - // re-sync with 3 vouchers + // Re-sync with all three vouchers. $this->actingAs($this->centreUser, 'store') - ->put($put_route, ['vouchers' => $this->testCodes]); + ->put($putRoute, ['vouchers' => $this->testCodes]); $currentBundle->refresh(); - $this->assertEquals(count($this->testCodes), $currentBundle->vouchers()->count()); + $this->assertSame(count($this->testCodes), $currentBundle->vouchers()->count()); - // sync without a voucher array AT ALL - does nothing. + // Sending no vouchers key at all leaves the bundle unchanged. $this->actingAs($this->centreUser, 'store') - ->put($put_route, []); + ->put($putRoute); $currentBundle->refresh(); - $this->assertEquals(count($this->testCodes), $currentBundle->vouchers()->count()); - - // sync with only a single empty voucher string erases the vouchers. - $this->assertEquals(3, $currentBundle->vouchers()->count()); + $this->assertSame(count($this->testCodes), $currentBundle->vouchers()->count()); + // A single empty string in the vouchers array clears the bundle. $this->actingAs($this->centreUser, 'store') - ->put($put_route, ['vouchers' => [''] ]); + ->put($putRoute, ['vouchers' => ['']]); $currentBundle->refresh(); - $this->assertEquals(0, $currentBundle->vouchers()->count()); + $this->assertSame(0, $currentBundle->vouchers()->count()); } + // ------------------------------------------------------------------------- + // Delete operations + // ------------------------------------------------------------------------- - public function testICannotDisburseAnEmptyBundle(): void + public function testICanDeleteTheCurrentBundle(): void { - // Setup bundle $currentBundle = $this->registration->currentBundle(); + $deleteCodes = ['TST0123455', 'TST0123456', 'TST0123457']; Auth::login($this->centreUser); + foreach ($deleteCodes as $code) { + $voucher = factory(Voucher::class)->state('printed')->create(['code' => $code]); + $voucher->applyTransition('dispatch'); + $voucher->bundle()->associate($currentBundle)->save(); + } + Auth::logout(); - // There should be one currentBundle with 3 vouchers - $this->assertCount(1, $this->registration->bundles); - - // Create a sensible place to have collected it. - $disbursementCentre = Auth::user()->centre->id; - - // Create a sensible date to have Collected on - $disbursementDate = Carbon::now()->startOfWeek()->format("Y-m-d"); - - // Find a carer for bundle - $collectingCarer = $this->registration->family->carers->first()->id; - - // Array all that - $data = [ - "collected_at" => $disbursementCentre, - "collected_on" => $disbursementDate, - "collected_by" => $collectingCarer - ]; + $this->assertSame(count($deleteCodes), $currentBundle->vouchers()->count()); - $route = route('store.registration.voucher-manager', ['registration' => $this->registration->id]); - $put_route = route('store.registration.vouchers.put', ['registration' => $this->registration->id]); + $deleteRoute = route('store.registration.vouchers.delete', ['registration' => $this->registration->id]); - // Attempt to submit - $response = $this->actingAs($this->centreUser, 'store') - ->visit($route) - ->put( - $put_route, - $data - ); + $this->actingAs($this->centreUser, 'store') + ->delete($deleteRoute); - // Confirm that we have supplied the appropriate error message to the session - $response->seeInSession('error_messages'); - $this->assertTrue($this->hasMatchingErrorMessage( - Session::get('error_messages'), - '/Action denied on empty bundle/' - )); + $currentBundle->refresh(); + $this->assertSame(0, $currentBundle->vouchers()->count()); - // Check the submission was a success - $this->followRedirects() - ->seePageIs($route) - ->assertResponseStatus(200); + // Every previously-bundled voucher should have a null bundle_id. + Voucher::whereIn('code', $deleteCodes) + ->each(function (Voucher $v) { + return $this->assertNull($v->bundle_id); + }); } - - public function testICannotAddAVoucherAllocatedInACentreIHaveAccessTo(): void + public function testICanDeleteANamedVoucher(): void { - $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); - $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); - - // Add a voucher to the registration's bundle - $this->actingAs($this->centreUser, 'store') - ->visit($route) - ->post( - $post_route, - ["start" => $this->testCodes[0]] - ); - - // Add a second registration in the same centre - $this->registrationTwo = factory(Registration::class)->create([ - 'centre_id' => $this->centre->id - ]); + /** @var Bundle $currentBundle */ + $currentBundle = $this->registration->currentBundle(); + $bundledCodes = ['TST0123455', 'TST0123456', 'TST0123457']; - $route_2 = route('store.registration.voucher-manager', [ 'registration' => $this->registrationTwo->id ]); - $post_route_2 = route('store.registration.vouchers.post', [ 'registration' => $this->registrationTwo->id ]); + Auth::login($this->centreUser); + foreach ($bundledCodes as $code) { + $voucher = factory(Voucher::class)->state('printed')->create(['code' => $code]); + $voucher->applyTransition('dispatch'); + $voucher->bundle()->associate($currentBundle)->save(); + } + Auth::logout(); - // Attempt to post the same voucher code into the second registration's bundle - $response = $this->actingAs($this->centreUser, 'store') - ->visit($route_2) - ->post( - $post_route_2, - ["start" => $this->testCodes[0]] - ); + $this->assertSame(count($bundledCodes), $currentBundle->vouchers()->count()); - // See we have no vouchers added. - /** @var Bundle $currentBundle */ - // Get our currentBundle - $currentBundle = $this->registrationTwo->currentBundle(); + /** @var Voucher $target */ + $target = $currentBundle->vouchers()->first(); + $deleteRoute = route('store.registration.voucher.delete', [ + 'registration' => $this->registration->id, + 'voucher' => $target->id, + ]); - // See that it's got no vouchers. - $this->assertEquals(0, $currentBundle->vouchers()->count()); + $this->actingAs($this->centreUser, 'store') + ->delete($deleteRoute); - // Check the expected error message is in the session - $response->seeInSession('error_messages'); - $entity = Family::getAlias($this->programme); - $this->assertTrue($this->hasMatchingErrorMessage( - Session::get('error_messages'), - '~These vouchers are currently allocated to a different ' . $entity . '. Click on the voucher number to view the other ' . $entity . '\'s record: ' . $this->testCodes[0] . '~' - )); + $currentBundle->refresh(); + $this->assertSame(count($bundledCodes) - 1, $currentBundle->vouchers()->count()); - // Check the expected error message is in the view - $this->followRedirects() - ->seeInElement('div[class="alert-message error"]', 'Click on the voucher number to view the other ' . $entity . '\'s record: ' . $this->testCodes[0] . ''); + $target->refresh(); + $this->assertNull($target->bundle_id); + $this->assertSame('dispatched', $target->currentstate); } + // ------------------------------------------------------------------------- + // Disbursement + // ------------------------------------------------------------------------- - public function testICannotAddAVoucherAllocatedInACentreIDoNotHaveAccessTo(): void + public function testICannotDisburseAnEmptyBundle(): void { - $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); - $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); - - // Add a voucher to the registration's bundle - $this->actingAs($this->centreUser, 'store') - ->visit($route) - ->post( - $post_route, - ["start" => $this->testCodes[1]] - ); - - // Create a second sponsor, centre and user - $this->sponsor2 = factory(Sponsor::class)->create(); - - $this->centre2 = factory(Centre::class)->create(["sponsor_id" => $this->sponsor2->id]); - - $this->centreUser2 = factory(CentreUser::class)->create([ - "name" => "second test user", - "email" => "testuser2@example.com", - "password" => bcrypt('test_user_pass2'), - "role" => "centre_user" - ]); - - $this->centreUser2->centres()->attach($this->centre2->id, ['homeCentre' => true]); - - // Add a registration to the second centre - $this->registrationTwo = factory(Registration::class)->create([ - 'centre_id' => $this->centre2->id - ]); - - $route_2 = route('store.registration.voucher-manager', [ 'registration' => $this->registrationTwo->id ]); - $post_route_2 = route('store.registration.vouchers.post', [ 'registration' => $this->registrationTwo->id ]); + $route = route('store.registration.voucher-manager', ['registration' => $this->registration->id]); + $putRoute = route('store.registration.vouchers.put', ['registration' => $this->registration->id]); - // Attempt to post the same voucher code into the second registration's bundle - $response = $this->actingAs($this->centreUser2, 'store') - ->visit($route_2) - ->post( - $post_route_2, - ["start" => $this->testCodes[1]] - ); + $data = [ + 'collected_at' => $this->centre->id, + 'collected_on' => Carbon::now()->startOfWeek()->format('Y-m-d'), + 'collected_by' => $this->registration->family->carers->first()->id, + ]; - // See we have no vouchers added. - /** @var Bundle $currentBundle */ - // Get our currentBundle - $currentBundle = $this->registrationTwo->currentBundle(); + $response = $this->actingAs($this->centreUser, 'store') + ->visit($route) + ->put($putRoute, $data); - // See that it's got no vouchers. - $this->assertEquals(0, $currentBundle->vouchers()->count()); - $entity = Family::getAlias($this->programme); - // Check the expected error message is in the session $response->seeInSession('error_messages'); - //dd(Session::get('error_messages')); $this->assertTrue($this->hasMatchingErrorMessage( Session::get('error_messages'), - '~These vouchers are allocated to a different ' . $entity . ' in a centre you can\'t access: ' . $this->testCodes[1] . '~' + '/Action denied on empty bundle/', )); - - // Check the expected error message is in the view - $this->followRedirects() - ->seeInElement('div[class="alert-message error"]', 'These vouchers are allocated to a different ' . $entity . ' in a centre you can\'t access: ' . $this->testCodes[1]); - } - - - public function testItCanAcceptAndCleanVouchersWithSpacesIn(): void - { - $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); - $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); - - // Create a voucherCode with a space in from the test list - $voucherCode = $this->testCodes[0]; - $randPos = rand(0, strlen($voucherCode)); - $voucherCode = substr_replace($voucherCode, " ", $randPos, 0); - - // Add a voucher; - $this->actingAs($this->centreUser, 'store') - ->post($post_route, ['start' => $voucherCode]); - $this->followRedirects() ->seePageIs($route) - ->assertResponseStatus(200) - ; - - /** @var Bundle $currentBundle */ - // Get our currentBundle - $currentBundle = $this->registration->currentBundle(); - - // See that it's got one voucher. - $this->assertEquals(1, $currentBundle->vouchers()->count()); + ->assertResponseStatus(200); } - - public function testItHasSparseFormDataCleanedBeforeProcessing(): void - { - $route = route('store.registration.voucher-manager', [ 'registration' => $this->registration->id ]); - $post_route = route('store.registration.vouchers.post', [ 'registration' => $this->registration->id ]); - - $data_null_end = [ - 'start' => $this->testCodes[0], - 'end' => null - ]; - - $data_blank_end = [ - 'start' => $this->testCodes[1], - 'end' => '' - ]; - - // Add null end voucher; - $this->actingAs($this->centreUser, 'store') - ->post( - $post_route, - $data_null_end - ); - - // Expect to see last record is testCode[0] - $last_voucher = $this->registration - ->currentBundle() - ->vouchers() - ->orderByDesc('id') - ->first(); - $this->assertEquals($this->testCodes[0], $last_voucher->code); - - // Add empty string end vouchers; - $this->actingAs($this->centreUser, 'store') - ->post( - $post_route, - $data_blank_end - ); - - // Expect to see last record is testCode[1] - $last_voucher = $this->registration - ->currentBundle() - ->vouchers() - ->orderByDesc('id') - ->first(); - $this->assertEquals($this->testCodes[1], $last_voucher->code); - } + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- /** - * Search the session's array of error messages for one that matches our regular expression. + * Search an array of session error messages for one matching the given regular expression. + * Handles both plain strings and HtmlString-derived arrays with a 'html' key. * - * @param (string|array)[] $errorMessages array - * @param string $regex - * @return bool whether a matching message was found or not + * @param array> $errorMessages */ private function hasMatchingErrorMessage(array $errorMessages, string $regex): bool { foreach ($errorMessages as $error) { - // If the error message is an array describing some HTML, extract the text, otherwise use as a string directly. - $string = is_array($error) && array_key_exists('html', $error) ? $error['html'] : $error; + $string = is_array($error) && array_key_exists('html', $error) + ? $error['html'] + : (string)$error; + if (preg_match($regex, $string)) { return true; } } + return false; } } From 18aeaecaf0e527029e674e2bb6d6f695e2cc17c1 Mon Sep 17 00:00:00 2001 From: charles strange Date: Sun, 19 Apr 2026 10:06:26 +0100 Subject: [PATCH 102/168] refactor: extract the form validations to formrequests --- .../Store/BundleControllerTest.php | 182 +----------------- .../StoreAppendBundleRequestTest.php | 162 ++++++++++++++++ .../StoreUpdateBundleRequestTest.php | 107 ++++++++++ 3 files changed, 270 insertions(+), 181 deletions(-) create mode 100644 tests/Unit/FormRequests/StoreAppendBundleRequestTest.php create mode 100644 tests/Unit/FormRequests/StoreUpdateBundleRequestTest.php diff --git a/tests/Unit/Controllers/Store/BundleControllerTest.php b/tests/Unit/Controllers/Store/BundleControllerTest.php index af49957de..3a65cac26 100644 --- a/tests/Unit/Controllers/Store/BundleControllerTest.php +++ b/tests/Unit/Controllers/Store/BundleControllerTest.php @@ -2,7 +2,6 @@ namespace Tests\Unit\Controllers\Store; -use App\Bundle; use App\Centre; use App\CentreUser; use App\Family; @@ -13,7 +12,6 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Session; -use PHPUnit\Framework\Attributes\DataProvider; use Tests\StoreTestCase; class BundleControllerTest extends StoreTestCase @@ -58,182 +56,6 @@ protected function setUp(): void Auth::logout(); } - // ------------------------------------------------------------------------- - // Append-vouchers validation — data provider - // ------------------------------------------------------------------------- - - /** - * @return array, string, string}> - * - * Config placeholders: config() is unavailable in static providers (they are constructed - * before the Laravel application boots). Two placeholders are used instead and resolved - * inside the test method once the container is live: - * :max: → config('arc.bundle_max_voucher_append') - * :max_plus_one: → config('arc.bundle_max_voucher_append') + 1 - */ - public static function appendVoucherValidationCases(): array - { - return [ - // --- start field (voucher-quantity absent branch) --- - 'start: absent with no data submitted' => [ - [], - 'start', - 'The start field is required when voucher-quantity is not present.', - ], - 'start: absent when only end supplied' => [ - ['end' => 'tst10001'], - 'start', - 'The start field is required when voucher-quantity is not present.', - ], - 'start: empty string stripped by prepareForValidation' => [ - ['start' => '', 'end' => 'tst10001'], - 'start', - 'The start field is required when voucher-quantity is not present.', - ], - 'start: value not found in vouchers table' => [ - ['start' => 'invalidVoucher', 'end' => 'tst10001'], - 'start', - 'The selected start is invalid.', - ], - - // --- end field --- - 'end: value not found in vouchers table' => [ - ['start' => 'tst09999', 'end' => 'invalidCode'], - 'end', - 'The selected end is invalid.', - ], - 'end: different sponsor prefix to start' => [ - ['start' => 'tst09999', 'end' => 'txt10000'], - 'end', - 'The end field must be the same sponsor as the start field.', - ], - 'end: lower sequence number than start' => [ - ['start' => 'tst10001', 'end' => 'tst09999'], - 'end', - 'The end field must be greater than the start field.', - ], - - // --- voucher-quantity field (early-return branch) --- - 'voucher-quantity: zero is below the allowed minimum of 1' => [ - ['voucher-quantity' => '0'], - 'voucher-quantity', - 'The voucher-quantity must be between 1 and :max:.', - ], - 'voucher-quantity: negative value is below the allowed minimum of 1' => [ - ['voucher-quantity' => '-1'], - 'voucher-quantity', - 'The voucher-quantity must be between 1 and :max:.', - ], - 'voucher-quantity: exceeds the configured maximum' => [ - ['voucher-quantity' => ':max_plus_one:'], - 'voucher-quantity', - 'The voucher-quantity must be between 1 and :max:.', - ], - 'voucher-quantity: required when start also absent' => [ - [], - 'voucher-quantity', - 'The voucher-quantity field is required when start is not present.', - ], - ]; - } - - #[DataProvider('appendVoucherValidationCases')] - public function testICannotSubmitInvalidValuesToAppendVouchers( - array $data, - string $field, - string $expectedMessage, - ): void { - $max = (string)config('arc.bundle_max_voucher_append'); - - // Resolve placeholders that cannot be evaluated inside a static data provider. - $data = array_map(static function (string $v) use ($max) { - return str_replace(':max_plus_one:', (string)((int)$max + 1), $v); - }, $data); - $expectedMessage = str_replace(':max:', $max, $expectedMessage); - - $route = route('store.registration.voucher-manager', ['registration' => $this->registration->id]); - $postRoute = route('store.registration.vouchers.post', ['registration' => $this->registration->id]); - - $this->actingAs($this->centreUser, 'store') - ->visit($route) - ->post($postRoute, $data); - - $errors = session('errors')->get($field); - - $this->assertNotEmpty($errors, "Expected validation errors for field '$field'"); - $this->assertContains($expectedMessage, $errors); - - $this->followRedirects() - ->seePageIs($route) - ->assertResponseStatus(200); - } - - // ------------------------------------------------------------------------- - // Disbursement validation — data provider - // ------------------------------------------------------------------------- - - /** - * @return array, string, string}> - */ - public static function disburseValidationCases(): array - { - return [ - 'collected_by missing' => [ - ['collected_at' => '1', 'collected_on' => '2018-07-21'], - 'collected_by', - 'The collected by field is required when collected at / collected on is present.', - ], - 'collected_at missing' => [ - ['collected_by' => '1', 'collected_on' => '2018-07-21'], - 'collected_at', - 'The collected at field is required when collected on / collected by is present.', - ], - 'collected_on missing' => [ - ['collected_at' => '1', 'collected_by' => '1'], - 'collected_on', - 'The collected on field is required when collected at / collected by is present.', - ], - 'collected_on wrong date format' => [ - ['collected_at' => '1', 'collected_on' => 'invalid', 'collected_by' => '1'], - 'collected_on', - 'The collected on does not match the format Y-m-d.', - ], - 'collected_at centre does not exist' => [ - ['collected_at' => '9999', 'collected_on' => '2018-07-21', 'collected_by' => '1'], - 'collected_at', - 'The selected collected at is invalid.', - ], - 'collected_by carer does not exist' => [ - ['collected_at' => '1', 'collected_on' => '2018-07-21', 'collected_by' => '9999'], - 'collected_by', - 'The selected collected by is invalid.', - ], - ]; - } - - #[DataProvider('disburseValidationCases')] - public function testIMustDisburseWithAllRelevantFields( - array $data, - string $field, - string $expectedMessage, - ): void { - $route = route('store.registration.voucher-manager', ['registration' => $this->registration->id]); - $putRoute = route('store.registration.vouchers.put', ['registration' => $this->registration->id]); - - $this->actingAs($this->centreUser, 'store') - ->visit($route) - ->put($putRoute, $data); - - $errors = session('errors')->get($field); - - $this->assertNotEmpty($errors, "Expected validation errors for field '$field'"); - $this->assertContains($expectedMessage, $errors); - - $this->followRedirects() - ->seePageIs($route) - ->assertResponseStatus(200); - } - // ------------------------------------------------------------------------- // Adding vouchers to a bundle // ------------------------------------------------------------------------- @@ -402,7 +224,7 @@ public function testItCanAcceptAndCleanVouchersWithSpacesIn(): void // Insert a space at a random position in the voucher code. $voucherCode = $this->testCodes[0]; - $voucherCode = substr_replace($voucherCode, ' ', random_int(0, strlen($voucherCode)), 0); + $voucherCode = substr_replace($voucherCode, ' ', rand(0, strlen($voucherCode)), 0); $this->actingAs($this->centreUser, 'store') ->post($postRoute, ['start' => $voucherCode]); @@ -506,7 +328,6 @@ public function testICanDeleteTheCurrentBundle(): void public function testICanDeleteANamedVoucher(): void { - /** @var Bundle $currentBundle */ $currentBundle = $this->registration->currentBundle(); $bundledCodes = ['TST0123455', 'TST0123456', 'TST0123457']; @@ -520,7 +341,6 @@ public function testICanDeleteANamedVoucher(): void $this->assertSame(count($bundledCodes), $currentBundle->vouchers()->count()); - /** @var Voucher $target */ $target = $currentBundle->vouchers()->first(); $deleteRoute = route('store.registration.voucher.delete', [ 'registration' => $this->registration->id, diff --git a/tests/Unit/FormRequests/StoreAppendBundleRequestTest.php b/tests/Unit/FormRequests/StoreAppendBundleRequestTest.php new file mode 100644 index 000000000..55b2b95bc --- /dev/null +++ b/tests/Unit/FormRequests/StoreAppendBundleRequestTest.php @@ -0,0 +1,162 @@ +centre = factory(Centre::class)->create(); + + $this->centreUser = factory(CentreUser::class)->create([ + 'name' => 'test user', + 'email' => 'testuser@example.com', + 'password' => bcrypt('test_user_pass'), + ]); + $this->centreUser->centres()->attach($this->centre->id, ['homeCentre' => true]); + + $this->registration = factory(Registration::class)->create([ + 'centre_id' => $this->centre->id, + ]); + + // Auth context is required by the voucher state-machine transition logger. + Auth::login($this->centreUser); + + foreach (['TST09999', 'TST10000', 'TST10001'] as $code) { + $voucher = factory(Voucher::class)->state('printed')->create(['code' => $code]); + $voucher->applyTransition('dispatch'); + } + + Auth::logout(); + } + + // ------------------------------------------------------------------------- + // Data provider + // ------------------------------------------------------------------------- + + /** + * @return array, string, string}> + * Config placeholders: config() is unavailable in static providers (they are constructed + * before the Laravel application boots). Two placeholders are used instead and resolved + * inside the test method once the container is live: + * :max: → config('arc.bundle_max_voucher_append') + * :max_plus_one: → config('arc.bundle_max_voucher_append') + 1 + */ + public static function validationCases(): array + { + return [ + // --- start field (voucher-quantity absent branch) --- + 'start: absent with no data submitted' => [ + [], + 'start', + 'The start field is required when voucher-quantity is not present.', + ], + 'start: absent when only end supplied' => [ + ['end' => 'tst10001'], + 'start', + 'The start field is required when voucher-quantity is not present.', + ], + 'start: empty string stripped by prepareForValidation' => [ + ['start' => '', 'end' => 'tst10001'], + 'start', + 'The start field is required when voucher-quantity is not present.', + ], + 'start: value not found in vouchers table' => [ + ['start' => 'invalidVoucher', 'end' => 'tst10001'], + 'start', + 'The selected start is invalid.', + ], + + // --- end field --- + 'end: value not found in vouchers table' => [ + ['start' => 'tst09999', 'end' => 'invalidCode'], + 'end', + 'The selected end is invalid.', + ], + 'end: different sponsor prefix to start' => [ + ['start' => 'tst09999', 'end' => 'txt10000'], + 'end', + 'The end field must be the same sponsor as the start field.', + ], + 'end: lower sequence number than start' => [ + ['start' => 'tst10001', 'end' => 'tst09999'], + 'end', + 'The end field must be greater than the start field.', + ], + + // --- voucher-quantity field (early-return branch) --- + 'voucher-quantity: zero is below the allowed minimum of 1' => [ + ['voucher-quantity' => '0'], + 'voucher-quantity', + 'The voucher-quantity must be between 1 and :max:.', + ], + 'voucher-quantity: negative value is below the allowed minimum of 1' => [ + ['voucher-quantity' => '-1'], + 'voucher-quantity', + 'The voucher-quantity must be between 1 and :max:.', + ], + 'voucher-quantity: exceeds the configured maximum' => [ + ['voucher-quantity' => ':max_plus_one:'], + 'voucher-quantity', + 'The voucher-quantity must be between 1 and :max:.', + ], + 'voucher-quantity: required when start also absent' => [ + [], + 'voucher-quantity', + 'The voucher-quantity field is required when start is not present.', + ], + ]; + } + + // ------------------------------------------------------------------------- + // Tests + // ------------------------------------------------------------------------- + + #[DataProvider('validationCases')] + public function testInvalidInputIsRejected( + array $data, + string $field, + string $expectedMessage, + ): void { + $max = (string)config('arc.bundle_max_voucher_append'); + + // Resolve placeholders that cannot be evaluated inside a static data provider. + $data = array_map(static function (string $v) use ($max) { + return str_replace(':max_plus_one:', (string)((int)$max + 1), $v); + }, $data); + $expectedMessage = str_replace(':max:', $max, $expectedMessage); + + $route = route('store.registration.voucher-manager', ['registration' => $this->registration->id]); + $postRoute = route('store.registration.vouchers.post', ['registration' => $this->registration->id]); + + $this->actingAs($this->centreUser, 'store') + ->visit($route) + ->post($postRoute, $data); + + $errors = session('errors')->get($field); + + $this->assertNotEmpty($errors, "Expected validation errors for field '{$field}'"); + $this->assertContains($expectedMessage, $errors); + + $this->followRedirects() + ->seePageIs($route) + ->assertResponseStatus(200); + } +} diff --git a/tests/Unit/FormRequests/StoreUpdateBundleRequestTest.php b/tests/Unit/FormRequests/StoreUpdateBundleRequestTest.php new file mode 100644 index 000000000..d9b43ac87 --- /dev/null +++ b/tests/Unit/FormRequests/StoreUpdateBundleRequestTest.php @@ -0,0 +1,107 @@ +centre = factory(Centre::class)->create(); + + $this->centreUser = factory(CentreUser::class)->create([ + 'name' => 'test user', + 'email' => 'testuser@example.com', + 'password' => bcrypt('test_user_pass'), + ]); + $this->centreUser->centres()->attach($this->centre->id, ['homeCentre' => true]); + + $this->registration = factory(Registration::class)->create([ + 'centre_id' => $this->centre->id, + ]); + } + + // ------------------------------------------------------------------------- + // Data provider + // ------------------------------------------------------------------------- + + /** + * @return array, string, string}> + */ + public static function validationCases(): array + { + return [ + 'collected_by missing' => [ + ['collected_at' => '1', 'collected_on' => '2018-07-21'], + 'collected_by', + 'The collected by field is required when collected at / collected on is present.', + ], + 'collected_at missing' => [ + ['collected_by' => '1', 'collected_on' => '2018-07-21'], + 'collected_at', + 'The collected at field is required when collected on / collected by is present.', + ], + 'collected_on missing' => [ + ['collected_at' => '1', 'collected_by' => '1'], + 'collected_on', + 'The collected on field is required when collected at / collected by is present.', + ], + 'collected_on wrong date format' => [ + ['collected_at' => '1', 'collected_on' => 'invalid', 'collected_by' => '1'], + 'collected_on', + 'The collected on does not match the format Y-m-d.', + ], + 'collected_at centre does not exist' => [ + ['collected_at' => '9999', 'collected_on' => '2018-07-21', 'collected_by' => '1'], + 'collected_at', + 'The selected collected at is invalid.', + ], + 'collected_by carer does not exist' => [ + ['collected_at' => '1', 'collected_on' => '2018-07-21', 'collected_by' => '9999'], + 'collected_by', + 'The selected collected by is invalid.', + ], + ]; + } + + // ------------------------------------------------------------------------- + // Tests + // ------------------------------------------------------------------------- + + #[DataProvider('validationCases')] + public function testInvalidInputIsRejected( + array $data, + string $field, + string $expectedMessage, + ): void { + $route = route('store.registration.voucher-manager', ['registration' => $this->registration->id]); + $putRoute = route('store.registration.vouchers.put', ['registration' => $this->registration->id]); + + $this->actingAs($this->centreUser, 'store') + ->visit($route) + ->put($putRoute, $data); + + $errors = session('errors')->get($field); + + $this->assertNotEmpty($errors, "Expected validation errors for field '{$field}'"); + $this->assertContains($expectedMessage, $errors); + + $this->followRedirects() + ->seePageIs($route) + ->assertResponseStatus(200); + } +} From d7c5f1d035836f110f06a3d8a87db5f4a1686926 Mon Sep 17 00:00:00 2001 From: charles strange Date: Sun, 19 Apr 2026 12:06:15 +0100 Subject: [PATCH 103/168] refactor: normalise form requests, abstracted disbursing --- .../Controllers/Store/BundleController.php | 83 ++++++++++--------- .../Requests/StoreRequestPaymentRequest.php | 35 ++++++++ .../Requests/StoreUpdateBundleRequest.php | 14 +--- app/Registration.php | 4 +- .../store/voucher-manager/collect.blade.php | 24 +++--- routes/store.php | 3 +- .../Store/BundleControllerTest.php | 37 --------- 7 files changed, 97 insertions(+), 103 deletions(-) create mode 100644 app/Http/Requests/StoreRequestPaymentRequest.php diff --git a/app/Http/Controllers/Store/BundleController.php b/app/Http/Controllers/Store/BundleController.php index e06eaff88..bcfc4dbc9 100644 --- a/app/Http/Controllers/Store/BundleController.php +++ b/app/Http/Controllers/Store/BundleController.php @@ -8,6 +8,7 @@ use App\Family; use App\Http\Controllers\Controller; use App\Http\Requests\StoreAppendBundleRequest; +use App\Http\Requests\StoreRequestPaymentRequest; use App\Http\Requests\StoreUpdateBundleRequest; use App\Registration; use App\Voucher; @@ -69,9 +70,9 @@ public function addVouchersToCurrentBundle( ): RedirectResponse { $managerRoute = $this->managerRoute($registration); - if ($request->filled('quantity')) { + if ($request->filled('voucher-quantity')) { try { - $voucherCodes = Voucher::claimFromPool((int) $request->input('quantity')) + $voucherCodes = Voucher::claimFromPool((int)$request->input('voucher-quantity')) ->pluck('code') ->all(); } catch (Throwable $e) { @@ -96,44 +97,42 @@ public function addVouchersToCurrentBundle( } /** - * Update (sync) vouchers on the current bundle, and optionally disburse it. + * Disburse the current bundle if collection details are present. */ public function update(StoreUpdateBundleRequest $request, Registration $registration): RedirectResponse { $managerRoute = $this->managerRoute($registration); - $successRoute = $managerRoute; + $bundle = $registration->currentBundle(); $errors = []; - /** @var Bundle $bundle */ - $bundle = $registration->currentBundle(); + if ($request->filled(['collected_at', 'collected_by', 'collected_on'])) { + $errors = $this->attemptDisbursal( + $bundle, + $request->only(['collected_at', 'collected_by', 'collected_on']) + ); + } - // --- Sync voucher codes if supplied --- - if ($request->exists('vouchers')) { - $rawCodes = array_filter($request->input('vouchers', [])); + $successRoute = empty($errors) ? route('store.registration.index') : $managerRoute; - $voucherCodes = $rawCodes !== [] - ? Voucher::cleanCodes(array_values($rawCodes)) - : []; + return $this->redirectAfterRequest($errors, $successRoute, $managerRoute, $bundle); + } - $errors = array_merge_recursive($errors, $bundle->syncVouchers($voucherCodes)); - } + /** + * Disburse the current bundle and trigger a payment request transition. + */ + public function requestPayment(StoreRequestPaymentRequest $request, Registration $registration): RedirectResponse + { + $managerRoute = $this->managerRoute($registration); + $bundle = $registration->currentBundle(); - // --- Disburse if collection details are present --- - if ($request->filled(['collected_at', 'collected_by', 'collected_on'])) { - if ($bundle->vouchers->isEmpty()) { - $errors['empty'] = true; - } else { - $errors = array_merge_recursive( - $errors, - $this->disburseBundle($bundle, $request->only(['collected_at', 'collected_by', 'collected_on'])), - ); + $errors = $this->attemptDisbursal($bundle, $request->only(['collected_at', 'collected_by', 'collected_on'])); - if (empty($errors)) { - $successRoute = route('store.registration.index'); - } - } + if (empty($errors)) { + // TODO: trigger payment transition on $bundle } + $successRoute = empty($errors) ? route('store.registration.index') : $managerRoute; + return $this->redirectAfterRequest($errors, $successRoute, $managerRoute, $bundle); } @@ -166,10 +165,25 @@ public function removeVoucherFromCurrentBundle( return $this->redirectAfterRequest($errors, $route, $route); } + /** + * Guard against an empty bundle then delegate to disburseBundle. + * Returns an error map on failure, or an empty array on success. + */ + private function attemptDisbursal(Bundle $bundle, array $inputs): array + { + if ($bundle->vouchers->isEmpty()) { + return ['empty' => true]; + } + + return ($this->disburseBundle($bundle, $inputs)) + ? ['transaction' => true] + : []; + } + /** * Attempt to mark a bundle as disbursed. */ - private function disburseBundle(Bundle $bundle, array $inputs): array + private function disburseBundle(Bundle $bundle, array $inputs): bool { try { $bundle->disbursed_at = Carbon::createFromFormat('Y-m-d', $inputs['collected_on']) @@ -179,8 +193,6 @@ private function disburseBundle(Bundle $bundle, array $inputs): array $bundle->disbursingCentre()->associate(Centre::findOrFail($inputs['collected_at'])); $bundle->disbursingUser()->associate(Auth::user()); $bundle->save(); - - return []; } catch (Throwable $e) { Log::error(sprintf( 'Bad transaction for %s@%s by service user %s', @@ -189,9 +201,9 @@ private function disburseBundle(Bundle $bundle, array $inputs): array Auth::id() ?? 'unauthenticated', )); Log::error($e->getTraceAsString()); - - return ['transaction' => true]; + return false; } + return true; } /** @@ -306,11 +318,4 @@ private function buildSuccessMessage(Bundle $bundle): string $name, ); } - - /** - * - */ - public function requestPayment(Request $request) { - // - } } diff --git a/app/Http/Requests/StoreRequestPaymentRequest.php b/app/Http/Requests/StoreRequestPaymentRequest.php new file mode 100644 index 000000000..d374373db --- /dev/null +++ b/app/Http/Requests/StoreRequestPaymentRequest.php @@ -0,0 +1,35 @@ + 'required_with_all:collected_at,collected_by|date_format:Y-m-d', + 'collected_at' => 'integer|required_with_all:collected_on,collected_by|exists:centres,id', + 'collected_by' => 'integer|required_with_all:collected_at,collected_on|exists:carers,id' + ]; + } +} diff --git a/app/Http/Requests/StoreUpdateBundleRequest.php b/app/Http/Requests/StoreUpdateBundleRequest.php index 0dd13e4b5..dab9376f3 100644 --- a/app/Http/Requests/StoreUpdateBundleRequest.php +++ b/app/Http/Requests/StoreUpdateBundleRequest.php @@ -8,10 +8,8 @@ class StoreUpdateBundleRequest extends FormRequest { /** * Determine if the user is authorized to make this request. - * - * @return bool */ - public function authorize() + public function authorize(): true { // TODO : determine of existing registration route protection is sufficient. return true; @@ -20,24 +18,18 @@ public function authorize() /** * Get the validation rules that apply to the request. - * - * @return array */ - public function rules() + public function rules(): array { /* * These rules validate that the form data is well-formed. * It is NOT responsible for the context validation of that data. */ - $rules = [ - // MAY be one, can be null, members must be distinct. - 'vouchers.*' => 'nullable|distinct|string', + return [ // Mutually dependent 'collected_on' => 'required_with_all:collected_at,collected_by|date_format:Y-m-d', 'collected_at' => 'integer|required_with_all:collected_on,collected_by|exists:centres,id', 'collected_by' => 'integer|required_with_all:collected_at,collected_on|exists:carers,id' ]; - - return $rules; } } diff --git a/app/Registration.php b/app/Registration.php index 0fed5b0ca..bc4465175 100644 --- a/app/Registration.php +++ b/app/Registration.php @@ -129,10 +129,8 @@ public function centre(): BelongsTo /** * Get the first un-disbursed bundle on a Registration for any Centre. * There should only be one... else make one if there are none. - * - * @return Model */ - public function currentBundle(): Model + public function currentBundle(): Bundle { $bundle = $this->bundles() ->where('disbursed_at', null) diff --git a/resources/views/store/voucher-manager/collect.blade.php b/resources/views/store/voucher-manager/collect.blade.php index bacb5c408..d06b095da 100644 --- a/resources/views/store/voucher-manager/collect.blade.php +++ b/resources/views/store/voucher-manager/collect.blade.php @@ -19,8 +19,8 @@
- - @foreach($carers as $carer) @endforeach @@ -31,11 +31,11 @@
- + @@ -45,8 +45,8 @@
- - + +
@@ -67,13 +67,13 @@ @pushonce('scripts') tags. + * If this setting is set to true then DOMPDF will automatically evaluate embedded PHP contained + * within tags. * - * Enabling this for documents you do not trust (e.g. arbitrary remote html - * pages) is a security risk. Set this option to false if you wish to process - * untrusted documents. + * ==== IMPORTANT ==== Enabling this for documents you do not trust (e.g. arbitrary remote html pages) + * is a security risk. + * Embedded scripts are run with the same level of system access available to dompdf. + * Set this option to false (recommended) if you wish to process untrusted documents. + * This setting may increase the risk of system exploit. + * Do not change this settings without understanding the consequences. + * Additional documentation is available on the dompdf wiki at: + * https://github.com/dompdf/dompdf/wiki * * @var bool */ - "DOMPDF_ENABLE_PHP" => true, + 'enable_php' => false, /** - * Enable inline Javascript + * Enable inline JavaScript * - * If this setting is set to true then DOMPDF will automatically insert - * JavaScript code contained within tags. + * If this setting is set to true then DOMPDF will automatically insert JavaScript code contained + * within tags as written into the PDF. + * NOTE: This is PDF-based JavaScript to be executed by the PDF viewer, + * not browser-based JavaScript executed by Dompdf. * * @var bool */ - "DOMPDF_ENABLE_JAVASCRIPT" => true, + 'enable_javascript' => true, /** * Enable remote file access * - * If this setting is set to true, DOMPDF will access remote sites for - * images and CSS files as required. - * This is required for part of test case www/test/image_variants.html through www/examples.php + * If this setting is set to true, DOMPDF will access remote sites for + * images and CSS files as required. + * + * ==== IMPORTANT ==== + * This can be a security risk, in particular in combination with isPhpEnabled and + * allowing remote html code to be passed to $dompdf = new DOMPDF(); $dompdf->load_html(...); + * This allows anonymous users to download legally doubtful internet content which on + * tracing back appears to being downloaded by your server, or allows malicious php code + * in remote html pages to be executed by your server with your account privileges. * - * Attention! - * This can be a security risk, in particular in combination with DOMPDF_ENABLE_PHP and - * allowing remote access to dompdf.php or on allowing remote html code to be passed to - * $dompdf = new DOMPDF(, $dompdf->load_html(..., - * This allows anonymous users to download legally doubtful internet content which on - * tracing back appears to being downloaded by your server, or allows malicious php code - * in remote html pages to be executed by your server with your account privileges. + * This setting may increase the risk of system exploit. Do not change + * this settings without understanding the consequences. Additional + * documentation is available on the dompdf wiki at: + * https://github.com/dompdf/dompdf/wiki * * @var bool */ - "DOMPDF_ENABLE_REMOTE" => true, + 'enable_remote' => false, /** - * A ratio applied to the fonts height to be more like browsers' line height + * List of allowed remote hosts + * + * Each value of the array must be a valid hostname. + * + * This will be used to filter which resources can be loaded in combination with + * isRemoteEnabled. If enable_remote is FALSE, then this will have no effect. + * + * Leave to NULL to allow any remote host. + * + * @var array|null */ - "DOMPDF_FONT_HEIGHT_RATIO" => 1.1, + 'allowed_remote_hosts' => null, /** - * Enable CSS float - * - * Allows people to disabled CSS float support - * @var bool + * A ratio applied to the fonts height to be more like browsers' line height */ - "DOMPDF_ENABLE_CSS_FLOAT" => false, - + 'font_height_ratio' => 1.1, /** - * Use the more-than-experimental HTML5 Lib parser + * Use the HTML5 Lib parser + * + * @deprecated This feature is now always on in dompdf 2.x + * + * @var bool */ - "DOMPDF_ENABLE_HTML5PARSER" => false, - - - ), - + 'enable_html5_parser' => true, + ], -); +]; diff --git a/resources/fonts/Helvetica-Bold.afm.php b/resources/fonts/Helvetica-Bold.afm.php deleted file mode 100644 index 6ebf90233..000000000 --- a/resources/fonts/Helvetica-Bold.afm.php +++ /dev/null @@ -1,572 +0,0 @@ - - array ( - 32 => 'space', - 160 => 'space', - 33 => 'exclam', - 34 => 'quotedbl', - 35 => 'numbersign', - 36 => 'dollar', - 37 => 'percent', - 38 => 'ampersand', - 146 => 'quoteright', - 40 => 'parenleft', - 41 => 'parenright', - 42 => 'asterisk', - 43 => 'plus', - 44 => 'comma', - 45 => 'hyphen', - 173 => 'hyphen', - 46 => 'period', - 47 => 'slash', - 48 => 'zero', - 49 => 'one', - 50 => 'two', - 51 => 'three', - 52 => 'four', - 53 => 'five', - 54 => 'six', - 55 => 'seven', - 56 => 'eight', - 57 => 'nine', - 58 => 'colon', - 59 => 'semicolon', - 60 => 'less', - 61 => 'equal', - 62 => 'greater', - 63 => 'question', - 64 => 'at', - 65 => 'A', - 66 => 'B', - 67 => 'C', - 68 => 'D', - 69 => 'E', - 70 => 'F', - 71 => 'G', - 72 => 'H', - 73 => 'I', - 74 => 'J', - 75 => 'K', - 76 => 'L', - 77 => 'M', - 78 => 'N', - 79 => 'O', - 80 => 'P', - 81 => 'Q', - 82 => 'R', - 83 => 'S', - 84 => 'T', - 85 => 'U', - 86 => 'V', - 87 => 'W', - 88 => 'X', - 89 => 'Y', - 90 => 'Z', - 91 => 'bracketleft', - 92 => 'backslash', - 93 => 'bracketright', - 94 => 'asciicircum', - 95 => 'underscore', - 145 => 'quoteleft', - 97 => 'a', - 98 => 'b', - 99 => 'c', - 100 => 'd', - 101 => 'e', - 102 => 'f', - 103 => 'g', - 104 => 'h', - 105 => 'i', - 106 => 'j', - 107 => 'k', - 108 => 'l', - 109 => 'm', - 110 => 'n', - 111 => 'o', - 112 => 'p', - 113 => 'q', - 114 => 'r', - 115 => 's', - 116 => 't', - 117 => 'u', - 118 => 'v', - 119 => 'w', - 120 => 'x', - 121 => 'y', - 122 => 'z', - 123 => 'braceleft', - 124 => 'bar', - 125 => 'braceright', - 126 => 'asciitilde', - 161 => 'exclamdown', - 162 => 'cent', - 163 => 'sterling', - 165 => 'yen', - 131 => 'florin', - 167 => 'section', - 164 => 'currency', - 39 => 'quotesingle', - 147 => 'quotedblleft', - 171 => 'guillemotleft', - 139 => 'guilsinglleft', - 155 => 'guilsinglright', - 150 => 'endash', - 134 => 'dagger', - 135 => 'daggerdbl', - 183 => 'periodcentered', - 182 => 'paragraph', - 149 => 'bullet', - 130 => 'quotesinglbase', - 132 => 'quotedblbase', - 148 => 'quotedblright', - 187 => 'guillemotright', - 133 => 'ellipsis', - 137 => 'perthousand', - 191 => 'questiondown', - 96 => 'grave', - 180 => 'acute', - 136 => 'circumflex', - 152 => 'tilde', - 175 => 'macron', - 168 => 'dieresis', - 184 => 'cedilla', - 151 => 'emdash', - 198 => 'AE', - 170 => 'ordfeminine', - 216 => 'Oslash', - 140 => 'OE', - 186 => 'ordmasculine', - 230 => 'ae', - 248 => 'oslash', - 156 => 'oe', - 223 => 'germandbls', - 207 => 'Idieresis', - 233 => 'eacute', - 159 => 'Ydieresis', - 247 => 'divide', - 221 => 'Yacute', - 194 => 'Acircumflex', - 225 => 'aacute', - 219 => 'Ucircumflex', - 253 => 'yacute', - 234 => 'ecircumflex', - 220 => 'Udieresis', - 218 => 'Uacute', - 203 => 'Edieresis', - 169 => 'copyright', - 229 => 'aring', - 224 => 'agrave', - 227 => 'atilde', - 154 => 'scaron', - 237 => 'iacute', - 251 => 'ucircumflex', - 226 => 'acircumflex', - 231 => 'ccedilla', - 222 => 'Thorn', - 179 => 'threesuperior', - 210 => 'Ograve', - 192 => 'Agrave', - 215 => 'multiply', - 250 => 'uacute', - 255 => 'ydieresis', - 238 => 'icircumflex', - 202 => 'Ecircumflex', - 228 => 'adieresis', - 235 => 'edieresis', - 205 => 'Iacute', - 177 => 'plusminus', - 166 => 'brokenbar', - 174 => 'registered', - 200 => 'Egrave', - 142 => 'Zcaron', - 208 => 'Eth', - 199 => 'Ccedilla', - 193 => 'Aacute', - 196 => 'Adieresis', - 232 => 'egrave', - 211 => 'Oacute', - 243 => 'oacute', - 239 => 'idieresis', - 212 => 'Ocircumflex', - 217 => 'Ugrave', - 254 => 'thorn', - 178 => 'twosuperior', - 214 => 'Odieresis', - 181 => 'mu', - 236 => 'igrave', - 190 => 'threequarters', - 153 => 'trademark', - 204 => 'Igrave', - 189 => 'onehalf', - 244 => 'ocircumflex', - 241 => 'ntilde', - 201 => 'Eacute', - 188 => 'onequarter', - 138 => 'Scaron', - 176 => 'degree', - 242 => 'ograve', - 249 => 'ugrave', - 209 => 'Ntilde', - 245 => 'otilde', - 195 => 'Atilde', - 197 => 'Aring', - 213 => 'Otilde', - 206 => 'Icircumflex', - 172 => 'logicalnot', - 246 => 'odieresis', - 252 => 'udieresis', - 240 => 'eth', - 158 => 'zcaron', - 185 => 'onesuperior', - 128 => 'Euro', - ), - 'isUnicode' => false, - 'FontName' => 'Helvetica-Bold', - 'FullName' => 'Helvetica Bold', - 'FamilyName' => 'Helvetica', - 'Weight' => 'Bold', - 'ItalicAngle' => '0', - 'IsFixedPitch' => 'false', - 'CharacterSet' => 'ExtendedRoman', - 'FontBBox' => - array ( - 0 => '-170', - 1 => '-228', - 2 => '1003', - 3 => '962', - ), - 'UnderlinePosition' => '-100', - 'UnderlineThickness' => '50', - 'Version' => '002.000', - 'EncodingScheme' => 'WinAnsiEncoding', - 'CapHeight' => '718', - 'XHeight' => '532', - 'Ascender' => '718', - 'Descender' => '-207', - 'StdHW' => '118', - 'StdVW' => '140', - 'StartCharMetrics' => '317', - 'C' => - array ( - 32 => 278.0, - 160 => 278.0, - 33 => 333.0, - 34 => 474.0, - 35 => 556.0, - 36 => 556.0, - 37 => 889.0, - 38 => 722.0, - 146 => 278.0, - 40 => 333.0, - 41 => 333.0, - 42 => 389.0, - 43 => 584.0, - 44 => 278.0, - 45 => 333.0, - 173 => 333.0, - 46 => 278.0, - 47 => 278.0, - 48 => 556.0, - 49 => 556.0, - 50 => 556.0, - 51 => 556.0, - 52 => 556.0, - 53 => 556.0, - 54 => 556.0, - 55 => 556.0, - 56 => 556.0, - 57 => 556.0, - 58 => 333.0, - 59 => 333.0, - 60 => 584.0, - 61 => 584.0, - 62 => 584.0, - 63 => 611.0, - 64 => 975.0, - 65 => 722.0, - 66 => 722.0, - 67 => 722.0, - 68 => 722.0, - 69 => 667.0, - 70 => 611.0, - 71 => 778.0, - 72 => 722.0, - 73 => 278.0, - 74 => 556.0, - 75 => 722.0, - 76 => 611.0, - 77 => 833.0, - 78 => 722.0, - 79 => 778.0, - 80 => 667.0, - 81 => 778.0, - 82 => 722.0, - 83 => 667.0, - 84 => 611.0, - 85 => 722.0, - 86 => 667.0, - 87 => 944.0, - 88 => 667.0, - 89 => 667.0, - 90 => 611.0, - 91 => 333.0, - 92 => 278.0, - 93 => 333.0, - 94 => 584.0, - 95 => 556.0, - 145 => 278.0, - 97 => 556.0, - 98 => 611.0, - 99 => 556.0, - 100 => 611.0, - 101 => 556.0, - 102 => 333.0, - 103 => 611.0, - 104 => 611.0, - 105 => 278.0, - 106 => 278.0, - 107 => 556.0, - 108 => 278.0, - 109 => 889.0, - 110 => 611.0, - 111 => 611.0, - 112 => 611.0, - 113 => 611.0, - 114 => 389.0, - 115 => 556.0, - 116 => 333.0, - 117 => 611.0, - 118 => 556.0, - 119 => 778.0, - 120 => 556.0, - 121 => 556.0, - 122 => 500.0, - 123 => 389.0, - 124 => 280.0, - 125 => 389.0, - 126 => 584.0, - 161 => 333.0, - 162 => 556.0, - 163 => 556.0, - 'fraction' => 167.0, - 165 => 556.0, - 131 => 556.0, - 167 => 556.0, - 164 => 556.0, - 39 => 238.0, - 147 => 500.0, - 171 => 556.0, - 139 => 333.0, - 155 => 333.0, - 'fi' => 611.0, - 'fl' => 611.0, - 150 => 556.0, - 134 => 556.0, - 135 => 556.0, - 183 => 278.0, - 182 => 556.0, - 149 => 350.0, - 130 => 278.0, - 132 => 500.0, - 148 => 500.0, - 187 => 556.0, - 133 => 1000.0, - 137 => 1000.0, - 191 => 611.0, - 96 => 333.0, - 180 => 333.0, - 136 => 333.0, - 152 => 333.0, - 175 => 333.0, - 'breve' => 333.0, - 'dotaccent' => 333.0, - 168 => 333.0, - 'ring' => 333.0, - 184 => 333.0, - 'hungarumlaut' => 333.0, - 'ogonek' => 333.0, - 'caron' => 333.0, - 151 => 1000.0, - 198 => 1000.0, - 170 => 370.0, - 'Lslash' => 611.0, - 216 => 778.0, - 140 => 1000.0, - 186 => 365.0, - 230 => 889.0, - 'dotlessi' => 278.0, - 'lslash' => 278.0, - 248 => 611.0, - 156 => 944.0, - 223 => 611.0, - 207 => 278.0, - 233 => 556.0, - 'abreve' => 556.0, - 'uhungarumlaut' => 611.0, - 'ecaron' => 556.0, - 159 => 667.0, - 247 => 584.0, - 221 => 667.0, - 194 => 722.0, - 225 => 556.0, - 219 => 722.0, - 253 => 556.0, - 'scommaaccent' => 556.0, - 234 => 556.0, - 'Uring' => 722.0, - 220 => 722.0, - 'aogonek' => 556.0, - 218 => 722.0, - 'uogonek' => 611.0, - 203 => 667.0, - 'Dcroat' => 722.0, - 'commaaccent' => 250.0, - 169 => 737.0, - 'Emacron' => 667.0, - 'ccaron' => 556.0, - 229 => 556.0, - 'Ncommaaccent' => 722.0, - 'lacute' => 278.0, - 224 => 556.0, - 'Tcommaaccent' => 611.0, - 'Cacute' => 722.0, - 227 => 556.0, - 'Edotaccent' => 667.0, - 154 => 556.0, - 'scedilla' => 556.0, - 237 => 278.0, - 'lozenge' => 494.0, - 'Rcaron' => 722.0, - 'Gcommaaccent' => 778.0, - 251 => 611.0, - 226 => 556.0, - 'Amacron' => 722.0, - 'rcaron' => 389.0, - 231 => 556.0, - 'Zdotaccent' => 611.0, - 222 => 667.0, - 'Omacron' => 778.0, - 'Racute' => 722.0, - 'Sacute' => 667.0, - 'dcaron' => 743.0, - 'Umacron' => 722.0, - 'uring' => 611.0, - 179 => 333.0, - 210 => 778.0, - 192 => 722.0, - 'Abreve' => 722.0, - 215 => 584.0, - 250 => 611.0, - 'Tcaron' => 611.0, - 'partialdiff' => 494.0, - 255 => 556.0, - 'Nacute' => 722.0, - 238 => 278.0, - 202 => 667.0, - 228 => 556.0, - 235 => 556.0, - 'cacute' => 556.0, - 'nacute' => 611.0, - 'umacron' => 611.0, - 'Ncaron' => 722.0, - 205 => 278.0, - 177 => 584.0, - 166 => 280.0, - 174 => 737.0, - 'Gbreve' => 778.0, - 'Idotaccent' => 278.0, - 'summation' => 600.0, - 200 => 667.0, - 'racute' => 389.0, - 'omacron' => 611.0, - 'Zacute' => 611.0, - 142 => 611.0, - 'greaterequal' => 549.0, - 208 => 722.0, - 199 => 722.0, - 'lcommaaccent' => 278.0, - 'tcaron' => 389.0, - 'eogonek' => 556.0, - 'Uogonek' => 722.0, - 193 => 722.0, - 196 => 722.0, - 232 => 556.0, - 'zacute' => 500.0, - 'iogonek' => 278.0, - 211 => 778.0, - 243 => 611.0, - 'amacron' => 556.0, - 'sacute' => 556.0, - 239 => 278.0, - 212 => 778.0, - 217 => 722.0, - 'Delta' => 612.0, - 254 => 611.0, - 178 => 333.0, - 214 => 778.0, - 181 => 611.0, - 236 => 278.0, - 'ohungarumlaut' => 611.0, - 'Eogonek' => 667.0, - 'dcroat' => 611.0, - 190 => 834.0, - 'Scedilla' => 667.0, - 'lcaron' => 400.0, - 'Kcommaaccent' => 722.0, - 'Lacute' => 611.0, - 153 => 1000.0, - 'edotaccent' => 556.0, - 204 => 278.0, - 'Imacron' => 278.0, - 'Lcaron' => 611.0, - 189 => 834.0, - 'lessequal' => 549.0, - 244 => 611.0, - 241 => 611.0, - 'Uhungarumlaut' => 722.0, - 201 => 667.0, - 'emacron' => 556.0, - 'gbreve' => 611.0, - 188 => 834.0, - 138 => 667.0, - 'Scommaaccent' => 667.0, - 'Ohungarumlaut' => 778.0, - 176 => 400.0, - 242 => 611.0, - 'Ccaron' => 722.0, - 249 => 611.0, - 'radical' => 549.0, - 'Dcaron' => 722.0, - 'rcommaaccent' => 389.0, - 209 => 722.0, - 245 => 611.0, - 'Rcommaaccent' => 722.0, - 'Lcommaaccent' => 611.0, - 195 => 722.0, - 'Aogonek' => 722.0, - 197 => 722.0, - 213 => 778.0, - 'zdotaccent' => 500.0, - 'Ecaron' => 667.0, - 'Iogonek' => 278.0, - 'kcommaaccent' => 556.0, - 'minus' => 584.0, - 206 => 278.0, - 'ncaron' => 611.0, - 'tcommaaccent' => 333.0, - 172 => 584.0, - 246 => 611.0, - 252 => 611.0, - 'notequal' => 549.0, - 'gcommaaccent' => 611.0, - 240 => 611.0, - 158 => 500.0, - 'ncommaaccent' => 611.0, - 185 => 333.0, - 'imacron' => 278.0, - 128 => 556.0, - ), - 'CIDtoGID_Compressed' => true, - 'CIDtoGID' => 'eJwDAAAAAAE=', - '_version_' => 6, -); \ No newline at end of file From 06ab5c655f50e986e2c1516f1308fda09de213e5 Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 7 May 2026 13:15:49 +0100 Subject: [PATCH 166/168] fix: set dompdf vars true --- config/dompdf.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/dompdf.php b/config/dompdf.php index 4e1bc27aa..8d22a84b3 100644 --- a/config/dompdf.php +++ b/config/dompdf.php @@ -250,7 +250,7 @@ * * @var bool */ - 'enable_php' => false, + 'enable_php' => true, /** * Enable inline JavaScript @@ -284,7 +284,7 @@ * * @var bool */ - 'enable_remote' => false, + 'enable_remote' => true, /** * List of allowed remote hosts From 32df849549328095a644be7d16ee3796144fb2ae Mon Sep 17 00:00:00 2001 From: charles strange Date: Thu, 7 May 2026 13:40:39 +0100 Subject: [PATCH 167/168] fix: classpath on a test --- tests/Feature/Service/AdminResetTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Feature/Service/AdminResetTest.php b/tests/Feature/Service/AdminResetTest.php index 256f62116..8299ac016 100644 --- a/tests/Feature/Service/AdminResetTest.php +++ b/tests/Feature/Service/AdminResetTest.php @@ -1,6 +1,6 @@ Date: Thu, 7 May 2026 17:00:53 +0100 Subject: [PATCH 168/168] fix: exclude contacts from debug mode. --- routes/data.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/routes/data.php b/routes/data.php index 8e6bf4763..b2103574b 100644 --- a/routes/data.php +++ b/routes/data.php @@ -5,6 +5,7 @@ use App\Http\Controllers\Service\Data\TraderController; use App\Http\Controllers\Service\Data\UserController; use App\Http\Controllers\Service\Data\VoucherController; +use App\Http\Middleware\IsNotProduction; use App\Jobs\ResetDemoEnvironment; use Illuminate\Support\Facades\Redirect; @@ -27,7 +28,9 @@ Route::get('markets', [MarketController::class, 'index'])->name('markets.index'); Route::get('traders', [TraderController::class, 'index'])->name('traders.index'); // Invokable - Route::get('families/contacts', FamilyContactsController::class)->name('families.contacts.download'); + Route::get('families/contacts', FamilyContactsController::class) + ->withoutMiddleware(IsNotProduction::class) + ->name('families.contacts.download'); // Temporary route for demo only. Route::get('reset', static function () {