From 6483087a806b798ba663d3a762294320ba2481ef Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Sun, 1 Nov 2026 10:42:30 +0000 Subject: [PATCH 01/36] Cover atomic configuration, event and route cache publication Reconcile Laravel framework PR 62117 against the frozen 13.x source at 5d7797528723107cd4f495326838a7d7d67fbb20. The cache commands already publish through Filesystem::replace, so port the missing assertions that cache creation uses atomic replacement and does not write in place. Consolidate redundant event and route success cases while retaining success output and file-existence assertions. Preserve the isolated config and route subprocess fixtures and the nullable replacement mode argument. All three complete test files passed before review; these reviewed changes were held intact for this batch to keep the preceding initial PR within its file limit. --- .../Foundation/Console/ConfigCacheCommandTest.php | 13 +++++++++++++ .../Foundation/Console/EventCacheCommandTest.php | 13 ++++++------- .../Foundation/Console/RouteCacheCommandTest.php | 8 +++++++- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/tests/Integration/Foundation/Console/ConfigCacheCommandTest.php b/tests/Integration/Foundation/Console/ConfigCacheCommandTest.php index 5a79c52676..cf27481958 100644 --- a/tests/Integration/Foundation/Console/ConfigCacheCommandTest.php +++ b/tests/Integration/Foundation/Console/ConfigCacheCommandTest.php @@ -65,6 +65,19 @@ public function testConfigurationCanBeCachedSuccessfully(): void $this->assertFileExists($this->app->getCachedConfigPath()); } + public function testConfigurationIsCachedAtomically(): void + { + $files = m::mock(Filesystem::class)->makePartial(); + $files->shouldReceive('replace')->once()->with($this->app->getCachedConfigPath(), m::type('string'), null)->passthru(); + $files->shouldNotReceive('put'); + + $this->app->instance('files', $files); + + $this->artisan('config:cache')->assertSuccessful(); + + $this->assertFileExists($this->app->getCachedConfigPath()); + } + public function testConfigurationCacheFailsWithNonSerializableValue(): void { $files = new Filesystem; diff --git a/tests/Integration/Foundation/Console/EventCacheCommandTest.php b/tests/Integration/Foundation/Console/EventCacheCommandTest.php index 4e9e27cfe7..d036408e42 100644 --- a/tests/Integration/Foundation/Console/EventCacheCommandTest.php +++ b/tests/Integration/Foundation/Console/EventCacheCommandTest.php @@ -19,19 +19,18 @@ protected function tearDown(): void parent::tearDown(); } - public function testEventsCacheCommandCreatesFile(): void + public function testEventsAreCachedAtomically(): void { - $this->artisan('event:cache') - ->assertSuccessful(); + $files = m::mock(Filesystem::class)->makePartial(); + $files->shouldReceive('replace')->once()->with($this->app->getCachedEventsPath(), m::type('string'), null)->passthru(); - $this->assertFileExists($this->app->getCachedEventsPath()); - } + $this->app->instance('files', $files); - public function testEventsCacheCommandOutputsSuccessMessage(): void - { $this->artisan('event:cache') ->expectsOutputToContain('Events cached successfully.') ->assertSuccessful(); + + $this->assertFileExists($this->app->getCachedEventsPath()); } public function testCachedFileContainsProviderEvents(): void diff --git a/tests/Integration/Foundation/Console/RouteCacheCommandTest.php b/tests/Integration/Foundation/Console/RouteCacheCommandTest.php index 2dbdb64136..603324beca 100644 --- a/tests/Integration/Foundation/Console/RouteCacheCommandTest.php +++ b/tests/Integration/Foundation/Console/RouteCacheCommandTest.php @@ -72,7 +72,7 @@ protected function tearDown(): void CleanupActions::run(...$actions); } - public function testRouteCacheSucceedsWithSourceRoutes(): void + public function testRoutesAreCachedAtomically(): void { $this->defineTestbenchRoutes( <<<'PHP' @@ -80,6 +80,12 @@ public function testRouteCacheSucceedsWithSourceRoutes(): void PHP ); + $files = m::mock(Filesystem::class)->makePartial(); + $files->shouldReceive('replace')->once()->with($this->app->getCachedRoutesPath(), m::type('string'), null)->passthru(); + $files->shouldNotReceive('put'); + + $this->app->instance('files', $files); + $this->artisan('route:cache') ->assertSuccessful() ->expectsOutputToContain('Routes cached successfully'); From 5ac328cdbf084bd40a501e1bb7c1e886b28df929 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Sun, 1 Nov 2026 12:29:23 +0000 Subject: [PATCH 02/36] Preserve database transactions when acquiring existing cache locks Port laravel/framework#62114 from 2184bbe43573547e5ecb1c5ab8527449e91bb61b, reconciled against the frozen 13.x source. Use conflict-safe insertion so a duplicate lock does not abort a PostgreSQL transaction before the conditional takeover update. Keep strict inserts on MySQL and MariaDB: INSERT IGNORE can truncate an oversized lock name and leave a lock that its original name cannot release. Preserve the SQLite read-first refusal, unique-violation-only takeover and existing ownership verification. Port both transaction regressions and add an oversized-name regression that fails under INSERT IGNORE. Verification: complete unit tests and integration tests on SQLite, PostgreSQL 14, MySQL 8.4 and MariaDB 10.11; static analysis and formatting pass. --- src/cache/src/DatabaseLock.php | 26 +++++++---- tests/Cache/CacheDatabaseLockTest.php | 30 ++++++------- .../Integration/Database/DatabaseLockTest.php | 44 +++++++++++++++++++ 3 files changed, 76 insertions(+), 24 deletions(-) diff --git a/src/cache/src/DatabaseLock.php b/src/cache/src/DatabaseLock.php index c090c35b34..0cf55f7f4b 100644 --- a/src/cache/src/DatabaseLock.php +++ b/src/cache/src/DatabaseLock.php @@ -83,10 +83,11 @@ protected function connection(): ConnectionInterface public function acquire(): bool { $connection = $this->connection(); + $driver = $connection->getDriverName(); - // Every SQLite write takes the database-wide write lock, even an insert that fails on the existing key or an + // Every SQLite write takes the database-wide write lock, even an insert that ignores the existing key or an // update that matches nothing, so a live lock held by another owner is refused with a read instead. - if ($connection->getDriverName() === 'sqlite') { + if ($driver === 'sqlite') { $owner = $connection->table($this->table) ->useWritePdo() ->where('key', $this->name) @@ -98,16 +99,23 @@ public function acquire(): bool } } - try { - $connection->table($this->table)->insert([ - 'key' => $this->name, - 'owner' => $this->owner, - 'expiration' => $this->expiresAt(), - ]); + $record = [ + 'key' => $this->name, + 'owner' => $this->owner, + 'expiration' => $this->expiresAt(), + ]; - $acquired = true; + try { + // MySQL's INSERT IGNORE truncates overlong names, leaving locks that cannot be released by their full name. + $acquired = in_array($driver, ['mysql', 'mariadb'], true) + ? $connection->table($this->table)->insert($record) + : $connection->table($this->table)->insertOrIgnore($record) > 0; } catch (UniqueConstraintViolationException) { // Only an existing row means the lock may still be taken over; other failures, such as a busy database, propagate. + $acquired = false; + } + + if (! $acquired) { $updated = $connection->table($this->table) ->where('key', $this->name) ->where(function ($query) { diff --git a/tests/Cache/CacheDatabaseLockTest.php b/tests/Cache/CacheDatabaseLockTest.php index cdc2709d5b..8f6ddad25b 100644 --- a/tests/Cache/CacheDatabaseLockTest.php +++ b/tests/Cache/CacheDatabaseLockTest.php @@ -28,12 +28,12 @@ public function testLockCanBeAcquired(): void { [$lock, $table] = $this->getLock(); - $table->shouldReceive('insert')->once()->with(m::on(function ($arg) { + $table->shouldReceive('insertOrIgnore')->once()->with(m::on(function ($arg) { return is_array($arg) && $arg['key'] === 'foo' && isset($arg['owner']) && is_int($arg['expiration']); - }))->andReturn(true); + }))->andReturn(1); $this->assertTrue($lock->acquire()); } @@ -65,11 +65,11 @@ public function testFractionalSecondAcquireAndRefreshNeverExpireBeforeRequestedD [$lock, $table] = $this->getLock(seconds: 1); $owner = $lock->owner(); - $table->shouldReceive('insert')->once()->with([ + $table->shouldReceive('insertOrIgnore')->once()->with([ 'key' => 'foo', 'owner' => $owner, 'expiration' => 1002, - ])->andReturn(true); + ])->andReturn(1); $this->assertTrue($lock->acquire()); @@ -86,8 +86,8 @@ public function testLockCanBeAcquiredIfAlreadyOwnedBySameOwner(): void [$lock, $table] = $this->getLock(); $owner = $lock->owner(); - // First attempt throws exception (key exists) - $table->shouldReceive('insert')->once()->andThrow(new UniqueConstraintViolationException('', '', [], new Exception)); + // First attempt inserts nothing because the key exists. + $table->shouldReceive('insertOrIgnore')->once()->andReturn(0); // So it tries to update $table->shouldReceive('where')->once()->with('key', 'foo')->andReturn($table); @@ -111,8 +111,8 @@ public function testLockCannotBeAcquiredIfAlreadyHeld(): void { [$lock, $table] = $this->getLock(); - // Insert fails - $table->shouldReceive('insert')->once()->andThrow(new UniqueConstraintViolationException('', '', [], new Exception)); + // Insert finds the existing key. + $table->shouldReceive('insertOrIgnore')->once()->andReturn(0); // Update fails too (someone else owns it) $table->shouldReceive('where')->once()->with('key', 'foo')->andReturn($table); @@ -139,7 +139,7 @@ public function testInsertFailureOtherThanAnExistingLockPropagatesWithoutTakingO [$lock, $table] = $this->getLock(); $failure = new QueryException('default', 'insert into "cache_locks"', [], new PDOException('SQLSTATE[HY000]: General error: 5 database is locked')); - $table->shouldReceive('insert')->once()->andThrow($failure); + $table->shouldReceive('insertOrIgnore')->once()->andThrow($failure); $table->shouldNotReceive('update'); try { @@ -161,7 +161,7 @@ public function testSqliteLockHeldByAnotherOwnerIsRefusedWithoutWriting(): void public function testSqliteLockIsInsertedWhenNoLiveLockExists(): void { $insert = m::mock(Builder::class); - $insert->shouldReceive('insert')->once()->andReturn(true); + $insert->shouldReceive('insertOrIgnore')->once()->andReturn(1); [$lock] = $this->getLock(driver: 'sqlite', tables: [$this->liveOwnerRead(static fn (): null => null), $insert]); @@ -178,7 +178,7 @@ public function testSqliteExpiredOrOwnLockIsTakenOverByTheConditionalUpdate(bool return $own ? $lock->owner() : null; }); $insert = m::mock(Builder::class); - $insert->shouldReceive('insert')->once()->andThrow(new UniqueConstraintViolationException('', '', [], new Exception)); + $insert->shouldReceive('insertOrIgnore')->once()->andReturn(0); $update = m::mock(Builder::class); $update->shouldReceive('where')->once()->with('key', 'foo')->andReturnSelf(); $update->shouldReceive('where')->once()->with(m::type(Closure::class))->andReturnSelf(); @@ -230,7 +230,7 @@ public function testExpiredLocksAreDeletedDuringAcquisition(): void { [$lock, $table] = $this->getLock(lockLottery: [1, 1]); // Always hit lottery - $table->shouldReceive('insert')->once()->andReturn(true); + $table->shouldReceive('insertOrIgnore')->once()->andReturn(1); // Lottery cleanup $table->shouldReceive('where')->once()->with('expiration', '<=', m::type('int'))->andReturn($table); @@ -245,7 +245,7 @@ public function testLockCanBeAcquiredWithoutAutomaticPruning(?array $lottery): v { [$lock, $table] = $this->getLock(lockLottery: $lottery); - $table->shouldReceive('insert')->once()->andReturn(true); + $table->shouldReceive('insertOrIgnore')->once()->andReturn(1); $this->assertTrue($lock->acquire()); } @@ -302,12 +302,12 @@ public function testLockWithDefaultTimeout(): void [$lock, $table] = $this->getLock(seconds: 0); - $table->shouldReceive('insert')->once()->with(m::on(function ($arg) use ($now) { + $table->shouldReceive('insertOrIgnore')->once()->with(m::on(function ($arg) use ($now) { return is_array($arg) && $arg['key'] === 'foo' && isset($arg['owner']) && $arg['expiration'] === $now->getTimestamp() + 86400; // Default timeout - }))->andReturn(true); + }))->andReturn(1); $this->assertTrue($lock->acquire()); } diff --git a/tests/Integration/Database/DatabaseLockTest.php b/tests/Integration/Database/DatabaseLockTest.php index 8978eea124..dc7eb1e48f 100644 --- a/tests/Integration/Database/DatabaseLockTest.php +++ b/tests/Integration/Database/DatabaseLockTest.php @@ -46,6 +46,22 @@ public function testLockCanBeAcquired(): void $otherLock->release(); } + public function testOverlongLockNamesFailWithoutLeavingATruncatedLock(): void + { + if (! in_array($this->driver, ['mysql', 'mariadb'], true)) { + $this->markTestSkipped('Only MySQL and MariaDB truncate overlong names with INSERT IGNORE.'); + } + + try { + Cache::store('database')->lock(str_repeat('a', 300))->get(); + $this->fail('The overlong lock name was accepted.'); + } catch (QueryException $exception) { + $this->assertSame('22001', $exception->getCode()); + } + + $this->assertSame(0, DB::table('cache_locks')->count()); + } + public function testOwnerCanAcquireItsLiveLockAgainWithinTheSameSecond(): void { // A frozen clock gives the second acquisition the expiration the lock already has, so its update changes nothing. @@ -132,6 +148,34 @@ public function testExpiredLockCanBeRetrieved(): void $otherLock->release(); } + public function testLockCannotBeAcquiredWhileHeldInsideTransaction(): void + { + $lock = Cache::driver('database')->lock('foo'); + $this->assertTrue($lock->get()); + + DB::transaction(function (): void { + $this->assertFalse(Cache::driver('database')->lock('foo')->get()); + + $this->assertSame(1, DB::table('cache_locks')->count()); + }); + + $lock->release(); + } + + public function testExpiredLockCanBeAcquiredInsideTransaction(): void + { + $lock = Cache::driver('database')->lock('foo'); + $this->assertTrue($lock->get()); + DB::table('cache_locks')->update(['expiration' => CarbonImmutable::now()->subDay()->getTimestamp()]); + + DB::transaction(function (): void { + $otherLock = Cache::driver('database')->lock('foo'); + $this->assertTrue($otherLock->get()); + + $otherLock->release(); + }); + } + public function testIsLocked(): void { $lock = Cache::driver('database')->lock('foo'); From ebbe8d38ec4b0612b9dda4c3f05a658125b9a497 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Sun, 1 Nov 2026 12:29:37 +0000 Subject: [PATCH 03/36] Retry unrestorable jobs and distinguish string-job data from object metadata Port laravel/framework#62124 from 6f3025721c3b5643649289f3cc54098a65374974, reconciled against the frozen 13.x source. Missing models no longer interrupt retrying subsequent jobs. Missing models, removed classes and string jobs retain the SQS driver defaults required by FIFO queues, including pooled connections. Mark string payloads before payload hooks and share their classification through JobName. Caller-owned command, commandName and batchId fields no longer trigger object deserialization, batch cleanup, misleading failed-job names or Telescope failures. Unmarked Laravel object payloads and custom createObjectPayload handlers retain their existing format and behavior; no handler allowlist or worker state is introduced. Normalize non-array string data only when Telescope captures it, retaining its protected array signatures and leaving dispatched data unchanged. Keep the conservative Sentry exclusion for opaque serialized values. Unmarked imported string payloads retain the upstream ambiguity because their format cannot distinguish caller data from custom object metadata. Verification: both upstream missing-model regressions, real-produced string payloads, custom-handler retryUntil, standard/FIFO options, timeout cleanup, failed-job listing and Telescope recording. The full queue suite passed before review trimming; all affected complete files pass after corrections. Full static analysis, targeted correction analysis and formatting pass. --- src/queue/src/Console/ListFailedCommand.php | 3 +- src/queue/src/Console/RetryCommand.php | 25 +++- src/queue/src/Jobs/Job.php | 3 +- src/queue/src/Jobs/JobName.php | 10 ++ src/queue/src/Queue.php | 2 + src/telescope/src/Watchers/JobWatcher.php | 18 ++- tests/Queue/QueueBeanstalkdQueueTest.php | 4 +- tests/Queue/QueueDatabaseQueueUnitTest.php | 6 +- tests/Queue/QueueJobTest.php | 46 +++++- tests/Queue/QueueListFailedCommandTest.php | 19 +++ tests/Queue/QueueRedisQueueTest.php | 20 +-- tests/Queue/QueueRetryCommandTest.php | 156 +++++++++++++++++++- tests/Telescope/Watchers/JobWatcherTest.php | 46 ++++++ 13 files changed, 327 insertions(+), 31 deletions(-) diff --git a/src/queue/src/Console/ListFailedCommand.php b/src/queue/src/Console/ListFailedCommand.php index be77eaf758..05abe54f61 100644 --- a/src/queue/src/Console/ListFailedCommand.php +++ b/src/queue/src/Console/ListFailedCommand.php @@ -6,6 +6,7 @@ use Hypervel\Console\Command; use Hypervel\Queue\Failed\FailedJobProviderInterface; +use Hypervel\Queue\Jobs\JobName; use Hypervel\Support\Arr; use Hypervel\Support\Collection; use Symfony\Component\Console\Attribute\AsCommand; @@ -93,7 +94,7 @@ private function extractJobName(string $payload): ?string return null; } - if (! isset($payload['data']['command'])) { + if (! JobName::hasJobObject($payload)) { return is_string($payload['job'] ?? null) ? $payload['job'] : null; } diff --git a/src/queue/src/Console/RetryCommand.php b/src/queue/src/Console/RetryCommand.php index ba9eac7d42..bc44564bda 100644 --- a/src/queue/src/Console/RetryCommand.php +++ b/src/queue/src/Console/RetryCommand.php @@ -11,8 +11,10 @@ use Hypervel\Contracts\Encryption\Encrypter; use Hypervel\Contracts\Events\Dispatcher; use Hypervel\Contracts\Queue\Queue; +use Hypervel\Database\Eloquent\ModelNotFoundException; use Hypervel\Queue\Events\JobRetryRequested; use Hypervel\Queue\Failed\FailedJobProviderInterface; +use Hypervel\Queue\Jobs\JobName; use Hypervel\Queue\QueuePoolProxy; use Hypervel\Queue\SqsQueue; use Hypervel\Support\Enumerable; @@ -188,11 +190,15 @@ protected function refreshRetryUntil(string $payload): string { $payload = json_decode($payload, true, flags: JSON_THROW_ON_ERROR); - if (! isset($payload['data']['command'])) { + if (! JobName::hasJobObject($payload)) { return json_encode($payload, JSON_THROW_ON_ERROR); } - $instance = $this->getInstanceFromPayload($payload); + try { + $instance = $this->getInstanceFromPayload($payload); + } catch (ModelNotFoundException) { + return json_encode($payload, JSON_THROW_ON_ERROR); + } if (is_object($instance) && ! $instance instanceof __PHP_Incomplete_Class && method_exists($instance, 'retryUntil')) { $retryUntil = $instance->retryUntil(); @@ -218,12 +224,21 @@ protected function getQueueableOptions(Queue $queue, stdClass $job): array $payload = json_decode($job->payload, true, flags: JSON_THROW_ON_ERROR); - if (! isset($payload['data']['command'])) { - return []; + try { + $instance = JobName::hasJobObject($payload) + ? $this->getInstanceFromPayload($payload) + : $payload['job']; + } catch (ModelNotFoundException) { + $instance = null; + } + + // Unrestorable jobs still need the driver's string-job defaults, including required FIFO message options. + if ($instance === null || $instance instanceof __PHP_Incomplete_Class) { + $instance = $payload['data']['commandName']; } $getOptions = fn (SqsQueue $queue) => $queue->getQueueableOptions( - $this->getInstanceFromPayload($payload), + $instance, $job->queue, $job->payload, ); diff --git a/src/queue/src/Jobs/Job.php b/src/queue/src/Jobs/Job.php index e6c5b3bac2..d87e234ad4 100644 --- a/src/queue/src/Jobs/Job.php +++ b/src/queue/src/Jobs/Job.php @@ -308,7 +308,8 @@ public function fail(?Throwable $e = null): void $this->markAsFailed(); try { - $commandName = $this->payload()['data']['commandName'] ?? false; + $payload = $this->payload(); + $commandName = JobName::hasJobObject($payload) ? ($payload['data']['commandName'] ?? false) : false; } catch (InvalidPayloadException) { $commandName = false; } diff --git a/src/queue/src/Jobs/JobName.php b/src/queue/src/Jobs/JobName.php index 8680e53a58..237aca720d 100644 --- a/src/queue/src/Jobs/JobName.php +++ b/src/queue/src/Jobs/JobName.php @@ -16,6 +16,16 @@ public static function parse(string $job): array return Str::parseCallback($job, 'fire'); } + /** + * Determine whether the payload carries an object job. + */ + public static function hasJobObject(array $payload): bool + { + // Check the marker before caller-owned data, which may still be an object in payload hooks. + // Unmarked payloads retain Laravel's format, including custom object-job handlers. + return ! isset($payload['stringJob']) && isset($payload['data']['command']); + } + /** * Get the resolved name of the queued job class. */ diff --git a/src/queue/src/Queue.php b/src/queue/src/Queue.php index 6a99b23852..44a1db8309 100644 --- a/src/queue/src/Queue.php +++ b/src/queue/src/Queue.php @@ -379,6 +379,8 @@ protected function createStringPayload(array|string $job, ?string $queue, mixed 'uuid' => (string) Str::uuid(), 'displayName' => is_string($job) ? explode('@', $job)[0] : null, 'job' => $job, + // Caller data must not be interpreted as serialized object-job metadata. + 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, diff --git a/src/telescope/src/Watchers/JobWatcher.php b/src/telescope/src/Watchers/JobWatcher.php index 9801176fc8..e6fffa7200 100644 --- a/src/telescope/src/Watchers/JobWatcher.php +++ b/src/telescope/src/Watchers/JobWatcher.php @@ -13,6 +13,7 @@ use Hypervel\Queue\Events\JobFailed; use Hypervel\Queue\Events\JobProcessed; use Hypervel\Queue\InvalidPayloadException; +use Hypervel\Queue\Jobs\JobName; use Hypervel\Queue\Queue; use Hypervel\Support\Arr; use Hypervel\Support\Str; @@ -22,6 +23,7 @@ use Hypervel\Telescope\ExtractProperties; use Hypervel\Telescope\ExtractTags; use Hypervel\Telescope\IncomingEntry; +use Hypervel\Telescope\JsonNormalizer; use Hypervel\Telescope\Telescope; use RuntimeException; @@ -60,7 +62,8 @@ public function recordJob(string $connection, ?string $queue, array $payload): ? return null; } - $job = isset($payload['data']['command']) + $hasJobObject = JobName::hasJobObject($payload); + $job = $hasJobObject ? get_class($payload['data']['command']) : $payload['job']; @@ -78,7 +81,7 @@ public function recordJob(string $connection, ?string $queue, array $payload): ? Telescope::recordJob( $entry = IncomingEntry::make($content) - ->withFamilyHash($content['data']['batchId'] ?? null) + ->withFamilyHash($hasJobObject ? ($content['data']['batchId'] ?? null) : null) ->tags($this->tags($payload)) ); @@ -207,8 +210,11 @@ protected function defaultJobData(string $connection, ?string $queue, array $pay */ protected function data(array $payload): array { - if (! isset($payload['data']['command'])) { - return $payload['data']; + if (! JobName::hasJobObject($payload)) { + // String handlers accept arbitrary JSON data; the entry's display contract requires an array. + return is_array($payload['data']) + ? $payload['data'] + : Arr::wrap(JsonNormalizer::normalize($payload['data'])); } return ExtractProperties::from( @@ -221,7 +227,7 @@ protected function data(array $payload): array */ protected function tags(array $payload): array { - if (! isset($payload['data']['command'])) { + if (! JobName::hasJobObject($payload)) { return []; } @@ -235,7 +241,7 @@ protected function tags(array $payload): array */ protected function updateBatch(array $payload): void { - if (! isset($payload['data']['command'])) { + if (! JobName::hasJobObject($payload)) { return; } diff --git a/tests/Queue/QueueBeanstalkdQueueTest.php b/tests/Queue/QueueBeanstalkdQueueTest.php index 4fce4c86bd..bbcc36f233 100644 --- a/tests/Queue/QueueBeanstalkdQueueTest.php +++ b/tests/Queue/QueueBeanstalkdQueueTest.php @@ -199,7 +199,7 @@ public function testPushProperlyPushesJobOntoBeanstalkd(): void $pheanstalk = $this->queue->getPheanstalk(); $pheanstalk->expects('useTube')->with(m::type(TubeName::class)); $pheanstalk->expects('useTube')->with(m::type(TubeName::class)); - $pheanstalk->expects('put')->times(2)->with(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => null]), 1024, 0, 60); + $pheanstalk->expects('put')->times(2)->with(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => null]), 1024, 0, 60); $this->queue->push('foo', ['data'], 'stack'); $this->queue->push('foo', ['data']); @@ -245,7 +245,7 @@ public function testDelayedPushProperlyPushesJobOntoBeanstalkd(): void $pheanstalk = $this->queue->getPheanstalk(); $pheanstalk->expects('useTube')->with(m::type(TubeName::class)); $pheanstalk->expects('useTube')->with(m::type(TubeName::class)); - $pheanstalk->expects('put')->times(2)->with(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => 5]), Pheanstalk::DEFAULT_PRIORITY, 5, Pheanstalk::DEFAULT_TTR); + $pheanstalk->expects('put')->times(2)->with(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => 5]), Pheanstalk::DEFAULT_PRIORITY, 5, Pheanstalk::DEFAULT_TTR); $this->queue->later(5, 'foo', ['data'], 'stack'); $this->queue->later(5, 'foo', ['data']); diff --git a/tests/Queue/QueueDatabaseQueueUnitTest.php b/tests/Queue/QueueDatabaseQueueUnitTest.php index 1d565ef4b9..dba089bbe6 100644 --- a/tests/Queue/QueueDatabaseQueueUnitTest.php +++ b/tests/Queue/QueueDatabaseQueueUnitTest.php @@ -265,7 +265,7 @@ public function testDelayedPushProperlyPushesJobOntoDatabase(DateInterval|DateTi $query->expects('insertGetId')->andReturnUsing(function (array $array) use ($uuid, $now): int { $this->assertSame('default', $array['queue']); - $this->assertSame(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => 1]), $array['payload']); + $this->assertSame(json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => 1]), $array['payload']); $this->assertEquals(0, $array['attempts']); $this->assertNull($array['reserved_at']); $this->assertSame(1002, $array['available_at']); @@ -433,14 +433,14 @@ public function testBulkBatchPushesOntoDatabase(): void $query->expects('insert')->andReturnUsing(function (array $records) use ($uuid, $now): bool { $this->assertEquals([[ 'queue' => 'queue', - 'payload' => json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => null]), + 'payload' => json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => null]), 'attempts' => 0, 'reserved_at' => null, 'available_at' => 1732502704, 'created_at' => 1732502704, ], [ 'queue' => 'queue', - 'payload' => json_encode(['uuid' => $uuid, 'displayName' => 'bar', 'job' => 'bar', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => null]), + 'payload' => json_encode(['uuid' => $uuid, 'displayName' => 'bar', 'job' => 'bar', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'delay' => null]), 'attempts' => 0, 'reserved_at' => null, 'available_at' => 1732502704, diff --git a/tests/Queue/QueueJobTest.php b/tests/Queue/QueueJobTest.php index 68697e1e64..fb4c564778 100644 --- a/tests/Queue/QueueJobTest.php +++ b/tests/Queue/QueueJobTest.php @@ -17,6 +17,7 @@ use Hypervel\Queue\Events\JobFailed; use Hypervel\Queue\InvalidPayloadException; use Hypervel\Queue\Jobs\Job; +use Hypervel\Queue\SyncQueue; use Hypervel\Queue\TimeoutExceededException; use Hypervel\Tests\TestCase; use Mockery as m; @@ -176,7 +177,10 @@ public function testBatchRollbackCancellationEscapesBeforeFurtherFailureCleanup( $job = new QueueJobPayloadStub(json_encode([ 'uuid' => 'job-uuid', 'job' => QueueJobTimeoutBatchStub::class, - 'data' => ['commandName' => QueueJobTimeoutBatchStub::class], + 'data' => [ + 'commandName' => QueueJobTimeoutBatchStub::class, + 'command' => serialize(new QueueJobTimeoutBatchStub), + ], ], JSON_THROW_ON_ERROR)); $timeout = TimeoutExceededException::forJob($job); $gate = $this->armCurrentCoroutineCancellation(); @@ -258,7 +262,10 @@ public function testBatchRollbackFailurePreservesTimeoutForFailedJobCleanup(?str $job = new QueueJobPayloadStub(json_encode([ 'uuid' => 'job-uuid', 'job' => QueueJobTimeoutBatchStub::class, - 'data' => ['commandName' => QueueJobTimeoutBatchStub::class], + 'data' => [ + 'commandName' => QueueJobTimeoutBatchStub::class, + 'command' => serialize(new QueueJobTimeoutBatchStub), + ], ], JSON_THROW_ON_ERROR)); $timeout = TimeoutExceededException::forJob($job); @@ -311,6 +318,41 @@ public static function failedJobDatabases(): array ]; } + public function testStringJobTimeoutDoesNotTreatCallerDataAsBatchMetadata(): void + { + $queue = new class extends SyncQueue { + /** + * Build a string-job payload through the real producer. + */ + public function payloadFor(string $job, array $data): string + { + return $this->createPayload($job, 'default', $data); + } + }; + $job = new QueueJobPayloadStub($queue->payloadFor( + QueueJobTimeoutBatchStub::class, + ['command' => 'backup:run', 'commandName' => 'backup:run'], + )); + $timeout = TimeoutExceededException::forJob($job); + $events = m::mock(Dispatcher::class); + $events->shouldReceive('hasListeners')->once()->with(JobFailed::class)->andReturnTrue(); + $events->shouldReceive('dispatch')->once() + ->withArgs(fn (JobFailed $event): bool => $event->job === $job && $event->exception === $timeout); + + $container = m::mock(Container::class); + $container->shouldNotReceive('make')->with(BatchRepository::class); + $container->shouldReceive('make')->with('config')->andReturn(new ConfigRepository(['queue' => ['failed' => ['driver' => null]]])); + $container->shouldReceive('make')->once()->with(QueueJobTimeoutBatchStub::class)->andReturn(new QueueJobTimeoutBatchStub); + $container->shouldReceive('make')->once()->with(Dispatcher::class)->andReturn($events); + $job->setContainer($container); + $job->setConnectionName('redis'); + + $job->fail($timeout); + + $this->assertTrue($job->hasFailed()); + $this->assertTrue($job->isDeleted()); + } + /** * Arm exact cancellation of the current coroutine at a controlled channel handoff. */ diff --git a/tests/Queue/QueueListFailedCommandTest.php b/tests/Queue/QueueListFailedCommandTest.php index 0c8ffbff80..bc63556acf 100644 --- a/tests/Queue/QueueListFailedCommandTest.php +++ b/tests/Queue/QueueListFailedCommandTest.php @@ -7,6 +7,7 @@ use Hypervel\Foundation\Application; use Hypervel\Queue\Console\ListFailedCommand; use Hypervel\Queue\Failed\FailedJobProviderInterface; +use Hypervel\Queue\SyncQueue; use Hypervel\Tests\TestCase; use Mockery as m; use Symfony\Component\Console\Input\ArrayInput; @@ -84,6 +85,24 @@ public function testEncryptedJobShowsUnderlyingClass(): void $this->assertStringNotContainsString('CallQueuedHandler', $output); } + public function testStringJobCommandDataDoesNotHideTheHandlerMethod(): void + { + $queue = new class extends SyncQueue { + /** + * Build a string payload through the real queue producer. + */ + public function payloadFor(string $job, array $data): string + { + return $this->createPayload($job, 'default', $data); + } + }; + $output = $this->runListFailedCommandWithRawPayload( + $queue->payloadFor('RunCommand@handle', ['command' => 'backup:run']), + ); + + $this->assertStringContainsString('RunCommand@handle', $output); + } + public function testMalformedPayloadDoesNotThrow(): void { // Malformed JSON in the payload column must not bubble up an exception; diff --git a/tests/Queue/QueueRedisQueueTest.php b/tests/Queue/QueueRedisQueueTest.php index fdeb94e40d..53dff20845 100644 --- a/tests/Queue/QueueRedisQueueTest.php +++ b/tests/Queue/QueueRedisQueueTest.php @@ -381,7 +381,7 @@ public function testPushProperlyPushesJobOntoRedis(): void $redisProxy = m::mock(RedisProxy::class); $redisProxy->shouldAllowMockingMethod('evalWithShaCache'); $redisProxy->expects('isCluster')->andReturn(false); - $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); + $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); $redis->expects('connection')->twice()->andReturn($redisProxy); $id = $queue->push('foo', ['data']); @@ -404,7 +404,7 @@ public function testPushProperlyPushesJobOntoRedisWithCustomPayloadHook(): void $redisProxy = m::mock(RedisProxy::class); $redisProxy->shouldAllowMockingMethod('evalWithShaCache'); $redisProxy->expects('isCluster')->andReturn(false); - $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'custom' => 'taylor', 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); + $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'custom' => 'taylor', 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); $redis->expects('connection')->twice()->andReturn($redisProxy); Queue::createPayloadUsing(function (string $connection, ?string $queue, array $payload): array { @@ -432,7 +432,7 @@ public function testJobQueueingAndQueuedEventsAreSkippedWhenNoListenersAreRegist $redisProxy = m::mock(RedisProxy::class); $redisProxy->shouldAllowMockingMethod('evalWithShaCache'); $redisProxy->expects('isCluster')->andReturn(false); - $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); + $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); $redis->expects('connection')->twice()->andReturn($redisProxy); $events = m::mock(Dispatcher::class); @@ -562,7 +562,7 @@ public function testPushRaisesFailedEventWhenRedisThrows(): void $this->assertSame('foo', $event->job); $this->assertSame('default', $event->connectionName); $this->assertNull($event->queue); - $this->assertSame(['uuid' => (string) $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null], $event->payload()); + $this->assertSame(['uuid' => (string) $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null], $event->payload()); return true; }) @@ -602,7 +602,7 @@ public function testPushProperlyPushesJobOntoRedisWithTwoCustomPayloadHook(): vo $redisProxy = m::mock(RedisProxy::class); $redisProxy->shouldAllowMockingMethod('evalWithShaCache'); $redisProxy->expects('isCluster')->andReturn(false); - $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'custom' => 'taylor', 'bar' => 'foo', 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); + $redisProxy->expects('evalWithShaCache')->with(LuaScripts::push(), ['queues:default', 'queues:default:notify'], [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'custom' => 'taylor', 'bar' => 'foo', 'id' => 'foo', 'attempts' => 0, 'delay' => null])]); $redis->expects('connection')->twice()->andReturn($redisProxy); Queue::createPayloadUsing(function (string $connection, ?string $queue, array $payload): array { @@ -639,7 +639,7 @@ public function testDelayedPushProperlyPushesJobOntoRedis(): void $redisProxy->expects('evalWithShaCache')->with( LuaScripts::later(), ['queues:default:delayed'], - [1002, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] + [1002, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] ); $redis->expects('connection')->twice()->andReturn($redisProxy); @@ -668,7 +668,7 @@ public function testDelayedPushWithDateTimeProperlyPushesJobOntoRedis(): void $redisProxy->expects('evalWithShaCache')->with( LuaScripts::later(), ['queues:default:delayed'], - [1002, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] + [1002, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] ); $redis->expects('connection')->twice()->andReturn($redisProxy); @@ -694,7 +694,7 @@ public function testDelayedPushWithIntervalNeverRunsBeforeRequestedLifetime(): v $redisProxy->expects('evalWithShaCache')->with( LuaScripts::later(), ['queues:default:delayed'], - [1002, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] + [1002, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] ); $redis->expects('connection')->twice()->andReturn($redisProxy); @@ -853,7 +853,7 @@ public function testPushUsesGetRedisKeyForLuaScript(): void $redisProxy->expects('evalWithShaCache')->with( LuaScripts::push(), ['queues:{default}', 'queues:{default}:notify'], - [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null])] + [json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => null])] ); $redis->expects('connection')->twice()->andReturn($redisProxy); @@ -920,7 +920,7 @@ public function testLaterUsesClusterSafeRedisKeyForDelayedSet(): void $redisProxy->expects('evalWithShaCache')->with( LuaScripts::later(), ['queues:{default}:delayed'], - [2, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] + [2, json_encode(['uuid' => $uuid, 'displayName' => 'foo', 'job' => 'foo', 'stringJob' => true, 'maxTries' => null, 'maxExceptions' => null, 'failOnTimeout' => false, 'backoff' => null, 'timeout' => null, 'data' => ['data'], 'createdAt' => $now->getTimestamp(), 'id' => 'foo', 'attempts' => 0, 'delay' => 1])] ); $redis->expects('connection')->twice()->andReturn($redisProxy); diff --git a/tests/Queue/QueueRetryCommandTest.php b/tests/Queue/QueueRetryCommandTest.php index 8a0dfca668..7e01a5ca5e 100644 --- a/tests/Queue/QueueRetryCommandTest.php +++ b/tests/Queue/QueueRetryCommandTest.php @@ -4,9 +4,12 @@ namespace Hypervel\Tests\Queue; +use Aws\Result; +use Aws\Sqs\SqsClient; use Generator; use Hypervel\Contracts\Events\Dispatcher; use Hypervel\Contracts\Queue\Queue as QueueContract; +use Hypervel\Database\Eloquent\ModelNotFoundException; use Hypervel\Foundation\Application; use Hypervel\Queue\Console\RetryCommand; use Hypervel\Queue\Events\JobRetryRequested; @@ -14,10 +17,12 @@ use Hypervel\Queue\Failed\NullFailedJobProvider; use Hypervel\Queue\QueueManager; use Hypervel\Queue\SqsQueue; +use Hypervel\Queue\SyncQueue; use Hypervel\Support\Collection; use Hypervel\Support\LazyCollection; use Hypervel\Tests\TestCase; use Mockery as m; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\TestWith; use RuntimeException; use stdClass; @@ -160,7 +165,9 @@ public function testItResetsAttemptsCountWhenRetryingAJob(): void $this->runRetryCommand(['id' => ['1']], $failer, ['database' => $queue]); } - public function testRefreshesTheRetryUntilTimestampWhenTheJobDefinesRetryUntil(): void + #[TestWith([false], 'unmarked object payload')] + #[TestWith([true], 'custom object handler')] + public function testRefreshesTheRetryUntilTimestampWhenTheJobDefinesRetryUntil(bool $customHandler): void { $failer = m::mock(FailedJobProviderInterface::class); $queue = m::mock(QueueContract::class); @@ -173,6 +180,14 @@ public function testRefreshesTheRetryUntilTimestampWhenTheJobDefinesRetryUntil() job: new QueueRetryCommandTestJobWithRetryUntil(retryUntil: 1234567890) ); + if ($customHandler) { + $producer = new QueueRetryCommandCustomQueue; + $producer->setContainer(new Application); + $payload = json_decode($producer->payloadFor(new QueueRetryCommandTestJobWithRetryUntil(1234567890)), true); + $payload['retryUntil'] = 0; + $job->payload = json_encode($payload); + } + $failer->shouldReceive('find')->once()->with('1')->andReturn($job); $queue->shouldReceive('pushRaw')->once()->with(m::on(function (string $payload): bool { return json_decode($payload, true)['retryUntil'] === 1234567890; @@ -533,6 +548,115 @@ public function testStopsRetryingWhenAJobInACollectionFailsToBePushed(): void $this->assertSame(['job-3'], array_keys($pendingJobs)); } + public function testRetriesRemainingJobsWhenAJobsModelNoLongerExists(): void + { + $failer = m::mock(FailedJobProviderInterface::class); + $queue = m::mock(QueueContract::class); + + $missing = $this->failedJob(id: 'job-1', connection: 'database', queue: 'default', job: new QueueRetryCommandTestJobWithMissingModel); + + $jobs = new Collection([ + 'job-1' => $missing, + 'job-2' => $this->failedJob(id: 'job-2', connection: 'database', queue: 'default'), + ]); + + $failer->shouldReceive('find')->once()->with('batch')->andReturn($jobs); + + $queue->shouldReceive('pushRaw')->once()->with($missing->payload, 'default', []); + $failer->shouldReceive('forget')->once()->with('job-1'); + + $queue->shouldReceive('pushRaw')->once()->with($this->retriedPayload(id: 'job-2'), 'default', []); + $failer->shouldReceive('forget')->once()->with('job-2'); + + $this->runRetryCommand(['id' => ['batch']], $failer, ['database' => $queue]); + } + + public function testRetriesStringJobsWithoutInterpretingTheirCommandData(): void + { + $failer = m::mock(FailedJobProviderInterface::class); + $queue = m::mock(QueueContract::class); + $data = ['command' => 'backup:run', 'commandName' => 'backup:run']; + $job = $this->failedJob(id: '1', connection: 'database', queue: 'default'); + $job->payload = (new QueueRetryCommandCustomQueue)->payloadFor('RunCommand@handle', $data); + + $failer->shouldReceive('find')->once()->with('1')->andReturn($job); + $queue->shouldReceive('pushRaw')->once()->with($job->payload, 'default', []); + $failer->shouldReceive('forget')->once()->with('1'); + + $this->runRetryCommand(['id' => ['1']], $failer, ['database' => $queue]); + } + + public function testRetriesJobWithoutQueueableOptionsWhenItsModelNoLongerExists(): void + { + $failer = m::mock(FailedJobProviderInterface::class); + $sqs = m::mock(SqsClient::class); + $queue = new SqsQueue($sqs, 'default', 'https://sqs.example.test'); + + $job = $this->failedJob(id: '1', connection: 'sqs', queue: 'default', job: new QueueRetryCommandTestJobWithMissingModel); + + $failer->shouldReceive('find')->once()->with('1')->andReturn($job); + $sqs->shouldReceive('sendMessage')->once()->with([ + 'QueueUrl' => 'https://sqs.example.test/default', + 'MessageBody' => $job->payload, + ])->andReturn(new Result(['MessageId' => 'message-1'])); + $failer->shouldReceive('forget')->once()->with('1'); + + $this->runRetryCommand(['id' => ['1']], $failer, ['sqs' => $queue]); + } + + #[DataProvider('jobsWithoutRestorableObjects')] + public function testRetriesSqsJobsWithDefaultOptionsWhenNoObjectCanBeRestored(string $queueName, array $payload): void + { + $failer = m::mock(FailedJobProviderInterface::class); + $sqs = m::mock(SqsClient::class); + $queue = new SqsQueue($sqs, $queueName, 'https://sqs.example.test'); + $job = $this->failedJob( + id: '1', + connection: 'sqs', + queue: $queueName, + payload: $payload, + job: new QueueRetryCommandTestJobWithMissingModel, + ); + + $failer->shouldReceive('find')->once()->with('1')->andReturn($job); + $sqs->shouldReceive('sendMessage')->once()->with(m::on(function (array $message) use ($queueName, $job): bool { + $expected = [ + 'QueueUrl' => 'https://sqs.example.test/' . $queueName, + 'MessageBody' => $job->payload, + ]; + + if (str_ends_with($queueName, '.fifo')) { + $this->assertIsString($message['MessageDeduplicationId']); + unset($message['MessageDeduplicationId']); + $expected['MessageGroupId'] = $queueName; + } + + $this->assertSame($expected, $message); + + return true; + }))->andReturn(new Result(['MessageId' => 'message-1'])); + $failer->shouldReceive('forget')->once()->with('1'); + + $this->runRetryCommand(['id' => ['1']], $failer, ['sqs' => $queue]); + } + + /** + * Provide retry payloads whose queue options need no restored object. + */ + public static function jobsWithoutRestorableObjects(): iterable + { + yield 'missing model on FIFO' => ['default.fifo', []]; + yield 'string job with command data on FIFO' => ['default.fifo', json_decode( + (new QueueRetryCommandCustomQueue)->payloadFor('RunCommand@handle', ['command' => 'backup:run', 'commandName' => 'backup:run']), + true, + )]; + + $incomplete = ['data' => ['commandName' => 'RemovedJob', 'command' => 'O:10:"RemovedJob":0:{}']]; + + yield 'removed class on FIFO' => ['default.fifo', $incomplete]; + yield 'removed class on standard' => ['default', $incomplete]; + } + public function testDispatchesTheEventBeforePushingAndForgetsTheJobAfterwards(): void { $failer = m::mock(FailedJobProviderInterface::class); @@ -711,6 +835,25 @@ private function runRetryCommand(array $input, FailedJobProviderInterface $faile } } +class QueueRetryCommandCustomQueue extends SyncQueue +{ + /** + * Build payloads through the real queue producer. + */ + public function payloadFor(object|string $job, mixed $data = ''): string + { + return $this->createPayload($job, 'default', $data); + } + + /** + * Replace the object handler as a custom queue driver may do. + */ + protected function createObjectPayload(object $job, ?string $queue): array + { + return array_replace(parent::createObjectPayload($job, $queue), ['job' => 'CustomObjectHandler@call']); + } +} + class QueueRetryCommandTestJob { } @@ -732,3 +875,14 @@ public function retryUntil(): int return $this->retryUntil; } } + +class QueueRetryCommandTestJobWithMissingModel +{ + /** + * Restore a job whose model has been deleted. + */ + public function __unserialize(array $data): void + { + throw (new ModelNotFoundException)->setModel('App\Models\User', [1]); + } +} diff --git a/tests/Telescope/Watchers/JobWatcherTest.php b/tests/Telescope/Watchers/JobWatcherTest.php index d6295f5e40..fd367a7b07 100644 --- a/tests/Telescope/Watchers/JobWatcherTest.php +++ b/tests/Telescope/Watchers/JobWatcherTest.php @@ -28,7 +28,9 @@ use Hypervel\Testbench\Attributes\WithMigration; use Hypervel\Testbench\Factories\UserFactory; use Hypervel\Tests\Telescope\FeatureTestCase; +use JsonSerializable; use Mockery as m; +use PHPUnit\Framework\Attributes\TestWith; use RuntimeException; use Throwable; @@ -175,6 +177,32 @@ public function testItHandlesPushedJobs(): void $this->assertSame(['framework' => 'Laravel'], $entry->content['data']); } + #[TestWith([ + ['command' => 'backup:run', 'commandName' => 'backup:run', 'batchId' => ['external' => 42]], + ['command' => 'backup:run', 'commandName' => 'backup:run', 'batchId' => ['external' => 42]], + ], 'caller metadata names')] + #[TestWith(['', ['']], 'default string data')] + #[TestWith([new PushedJobData, ['command' => 'backup:run']], 'JSON serializable data')] + public function testStringJobsPreserveCallerDataWithoutInterpretingItAsMetadata(mixed $data, array $expectedData): void + { + $this->app->make(QueueManager::class) + ->connection('database') + ->push(MyPushedJobClass::class . '@handle', $data); + $this->artisan('queue:work', [ + 'connection' => 'database', + '--once' => true, + ]); + + $entry = $this->loadTelescopeEntries()->first(); + $this->assertSame(EntryType::JOB, $entry->type); + $this->assertSame('processed', $entry->content['status']); + $this->assertSame('database', $entry->content['connection']); + $this->assertSame(MyPushedJobClass::class, $entry->content['name']); + $this->assertSame('default', $entry->content['queue']); + $this->assertSame($expectedData, $entry->content['data']); + $this->assertNull($entry->family_hash); + } + public function testJobCanHandleDeletedSerializedModel(): void { $user = UserFactory::new()->create(); @@ -431,6 +459,24 @@ public function fire(Job $job, array $data) { throw new PushedJobFailedException; } + + /** + * Handle a string job successfully. + */ + public function handle(Job $job, mixed $data): void + { + } +} + +class PushedJobData implements JsonSerializable +{ + /** + * Publish caller data independently of the object's properties. + */ + public function jsonSerialize(): array + { + return ['command' => 'backup:run']; + } } class PushedJobFailedException extends Exception From 31f71cd6f9a9f7ca6d00b43880c7d1bdd25da21a Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Sun, 1 Nov 2026 12:29:45 +0000 Subject: [PATCH 04/36] Clear the current route before each coroutine-local dispatch Port laravel/framework#62123 from 835946cb1608a309be92141feae6be438639fbff, reconciled against the frozen 13.x source. A route miss must not expose the route from an earlier dispatch in the same coroutine. Reset only the coroutine-local current route at the public dispatch boundary, preserving request isolation and dispatchToRoute customization. Port every upstream regression assertion; the test fails before the reset and the complete RoutingRouteTest passes afterward. Static analysis and formatting pass. --- src/routing/src/Router.php | 2 ++ tests/Routing/RoutingRouteTest.php | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/routing/src/Router.php b/src/routing/src/Router.php index 48e0362529..f9b9e93ace 100644 --- a/src/routing/src/Router.php +++ b/src/routing/src/Router.php @@ -609,6 +609,8 @@ public function respondWithRoute(string $name): SymfonyResponse public function dispatch(Request $request): SymfonyResponse { CoroutineContext::set(self::CURRENT_REQUEST_CONTEXT_KEY, $request); + // A failed match must not retain an earlier dispatch's route in this coroutine. + CoroutineContext::set(self::CURRENT_ROUTE_CONTEXT_KEY, null); return $this->dispatchToRoute($request); } diff --git a/tests/Routing/RoutingRouteTest.php b/tests/Routing/RoutingRouteTest.php index a11ad345f0..9c16d29fce 100644 --- a/tests/Routing/RoutingRouteTest.php +++ b/tests/Routing/RoutingRouteTest.php @@ -820,6 +820,26 @@ public function testRoutesDontMatchNonMatchingPathsWithLeadingOptionals(): void $this->assertSame('25', $router->dispatch(Request::create('foo/bar', 'GET'))->getContent()); } + public function testCurrentRouteIsResetWhenTheNextRequestDoesNotMatch(): void + { + $router = $this->getRouter(); + $router->get('invoices/{invoice}', fn (string $invoice): string => $invoice)->name('invoices.show'); + + $this->assertSame('123', $router->dispatch(Request::create('invoices/123', 'GET'))->getContent()); + $this->assertSame('invoices.show', $router->currentRouteName()); + + try { + $router->dispatch(Request::create('missing', 'GET')); + + $this->fail('A NotFoundHttpException should have been thrown.'); + } catch (NotFoundHttpException) { + } + + $this->assertNull($router->current()); + $this->assertNull($router->currentRouteName()); + $this->assertFalse($router->is('invoices.show')); + } + public function testRoutesDontMatchNonMatchingDomain(): void { $this->expectException(NotFoundHttpException::class); From 2a0c7c0eaa9df50a3a98f7f90d10b121c801a2f0 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Sun, 1 Nov 2026 17:24:35 +0000 Subject: [PATCH 05/36] Cover Redis tag pruning with SCAN_PREFIX Port laravel/framework#62122 from dd4184fd9306 at the recorded 13.x target 5d7797528723107cd4f495326838a7d7d67fbb20. Exercise the existing SafeScan path with a prefixed phpredis connection, preserving logical match patterns and prefix-stripped tag removal. Keep the native pruning implementation. Validation: complete CacheRedisStoreTest passes (20 tests, 62 assertions); batch formatting and source/type analysis pass. --- tests/Cache/CacheRedisStoreTest.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/Cache/CacheRedisStoreTest.php b/tests/Cache/CacheRedisStoreTest.php index a575f40a27..d9cb87f375 100755 --- a/tests/Cache/CacheRedisStoreTest.php +++ b/tests/Cache/CacheRedisStoreTest.php @@ -12,6 +12,7 @@ use Hypervel\Tests\Cache\Redis\RedisCacheTestCase; use Hypervel\Tests\Redis\Fixtures\FakeRedisClient; use Mockery as m; +use Redis; use stdClass; class CacheRedisStoreTest extends RedisCacheTestCase @@ -256,4 +257,24 @@ public function testFlushStaleTagsStopsScanningWhenTheCursorReachesZero(): void $this->assertSame(1, $client->getScanCallCount()); } + + public function testFlushStaleTagsDoesNotDoublePrefixTheMatchPattern(): void + { + $connection = $this->mockConnection(); + $connection->allows('getShouldTransform')->andReturn(false); + $connection->allows('getOption')->with(Redis::OPT_PREFIX)->andReturn('app_'); + $connection->allows('getOption')->with(Redis::OPT_SCAN)->andReturn(Redis::SCAN_PREFIX); + $connection->expects('scan') + ->with(m::any(), 'prefix:_all:tag:*:entries', 1000) + ->andReturnUsing(function (mixed &$cursor): array { + $cursor = 0; + + return ['app_prefix:_all:tag:foo:entries']; + }); + $connection->expects('zRemRangeByScore')->with('prefix:_all:tag:foo:entries', '0', m::any())->andReturn(0); + $connection->allows('zscan')->andReturn(false); + $connection->allows('zCard')->andReturn(0); + + $this->createStore($connection, tagMode: 'all')->flushStaleTags(); + } } From 10156b97109e8d80a082c6148653b55e021ff516 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Sun, 1 Nov 2026 17:24:35 +0000 Subject: [PATCH 06/36] Add the session garbage collection command Port laravel/framework#62104 and #62150 from 61073964b7c5 and d8f93dacc8a0 at 13.x target 5d7797528723107cd4f495326838a7d7d67fbb20. Register session:garbage-collect through the package provider, pass the configured session lifetime to the handler, and document scheduled cleanup and Redis expiry behavior. Validation: registered-command test passes (1 test, 3 assertions), command signatures pass (115 tests, 690 assertions), and batch source/type/skeleton analysis passes. --- src/docs/session.md | 20 +++++++++++ .../Console/SessionGarbageCollectCommand.php | 35 +++++++++++++++++++ src/session/src/SessionServiceProvider.php | 1 + tests/Console/Fixtures/command_signatures.php | 3 ++ .../SessionGarbageCollectCommandTest.php | 27 ++++++++++++++ 5 files changed, 86 insertions(+) create mode 100644 src/session/src/Console/SessionGarbageCollectCommand.php create mode 100644 tests/Session/SessionGarbageCollectCommandTest.php diff --git a/src/docs/session.md b/src/docs/session.md index c50325975e..9106ce903a 100644 --- a/src/docs/session.md +++ b/src/docs/session.md @@ -3,6 +3,7 @@ - [Introduction](#introduction) - [Configuration](#configuration) - [Driver Prerequisites](#driver-prerequisites) + - [Garbage Collection](#garbage-collection) - [Interacting With the Session](#interacting-with-the-session) - [Retrieving Data](#retrieving-data) - [Storing Data](#storing-data) @@ -88,6 +89,25 @@ Redis stores session payloads directly rather than routing them through a cache To list and invalidate a user's Redis sessions, enable `SESSION_TRACK_USER_SESSIONS`. This feature requires phpredis 6.3.0 or later and Redis 8.0 or Valkey 9.0 or later. You may leave this option disabled if your application does not provide session-management controls. + +### Garbage Collection + +The file and database session drivers remove expired sessions using a request-based lottery. You may run cleanup explicitly using the `session:garbage-collect` Artisan command: + +```shell +php artisan session:garbage-collect +``` + +To move cleanup out of requests, [schedule the command](/docs/{{version}}/scheduling) and set the `lottery` option in `config/session.php` to `[0, 100]`: + +```php +use Hypervel\Support\Facades\Schedule; + +Schedule::command('session:garbage-collect')->hourly(); +``` + +The command uses your configured session driver and lifetime. Redis sessions expire automatically and do not need scheduled garbage collection. + ## Interacting With the Session diff --git a/src/session/src/Console/SessionGarbageCollectCommand.php b/src/session/src/Console/SessionGarbageCollectCommand.php new file mode 100644 index 0000000000..332543b89c --- /dev/null +++ b/src/session/src/Console/SessionGarbageCollectCommand.php @@ -0,0 +1,35 @@ +hypervel->make('session') + ->driver() + ->getHandler() + ->gc($this->hypervel->make('config')->integer('session.lifetime') * 60); + + $this->components->info('Session garbage collector ran successfully.'); + } +} diff --git a/src/session/src/SessionServiceProvider.php b/src/session/src/SessionServiceProvider.php index 119d86c980..7bd22f2f4c 100644 --- a/src/session/src/SessionServiceProvider.php +++ b/src/session/src/SessionServiceProvider.php @@ -17,6 +17,7 @@ public function register(): void $this->registerSessionDriver(); $this->commands([ + Console\SessionGarbageCollectCommand::class, Console\SessionTableCommand::class, ]); } diff --git a/tests/Console/Fixtures/command_signatures.php b/tests/Console/Fixtures/command_signatures.php index 83db31378d..54c8818a5a 100644 --- a/tests/Console/Fixtures/command_signatures.php +++ b/tests/Console/Fixtures/command_signatures.php @@ -4225,6 +4225,9 @@ ], ], ], + \Hypervel\Session\Console\SessionGarbageCollectCommand::class => [ + 'name' => 'session:garbage-collect', + ], \Hypervel\Session\Console\SessionTableCommand::class => [ 'name' => 'make:session-table', 'aliases' => [ diff --git a/tests/Session/SessionGarbageCollectCommandTest.php b/tests/Session/SessionGarbageCollectCommandTest.php new file mode 100644 index 0000000000..531ef571b0 --- /dev/null +++ b/tests/Session/SessionGarbageCollectCommandTest.php @@ -0,0 +1,27 @@ +expects('gc')->with(420)->andReturn(2); + $this->app->make(SessionManager::class)->extend('custom', fn () => $handler); + + $this->artisan('session:garbage-collect') + ->expectsOutputToContain('Session garbage collector ran successfully.') + ->assertSuccessful(); + } +} From 9f8892c8b22bd71c6cf19681d78c6a8812576a1e Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Sun, 1 Nov 2026 17:24:35 +0000 Subject: [PATCH 07/36] Update the shared Vite toolchain and rebuild frontend assets Port laravel/framework#62132 (1e8b7383762e) and laravel/ai#1166 (ac4b35b00c714a91c4f76d246898d0d77f84deac). Use Vite+ 1.4.0, which bundles Vite 8.3.5 and Rolldown 1.2.15, through the existing workspace catalog and override. Rebuild the renderer, Horizon, Telescope and Workbench assets without adding another Vite installation. Validation: frozen dependency installation, frontend source checks, all five builds, 256 JavaScript tests, 159 cached-route tests and generated Wayfinder type checking pass. --- pnpm-lock.yaml | 895 +++++++++++------- pnpm-workspace.yaml | 6 +- .../exceptions/renderer/dist/scripts.js | 6 +- src/horizon/dist/app.js | 16 +- src/telescope/dist/app.js | 16 +- .../public/build/assets/app-CvQ5XWTR.js | 5 - .../public/build/assets/app-RtO928pj.js | 5 + src/workbench/public/build/manifest.json | 2 +- 8 files changed, 556 insertions(+), 395 deletions(-) delete mode 100644 src/workbench/public/build/assets/app-CvQ5XWTR.js create mode 100644 src/workbench/public/build/assets/app-RtO928pj.js diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5fc31bd114..790220bbba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,11 +55,11 @@ catalogs: specifier: ^7.0.2 version: 7.0.2 vite: - specifier: npm:@voidzero-dev/vite-plus-core@1.3.0 - version: 1.3.0 + specifier: npm:@voidzero-dev/vite-plus-core@1.4.0 + version: 1.4.0 vite-plus: - specifier: 1.3.0 - version: 1.3.0 + specifier: 1.4.0 + version: 1.4.0 vue: specifier: ^3.5.44 version: 3.5.44 @@ -71,7 +71,7 @@ catalogs: version: 4.6.4 overrides: - vite@*: npm:@voidzero-dev/vite-plus-core@1.3.0 + vite@*: npm:@voidzero-dev/vite-plus-core@1.4.0 vitest@*: 5.0.5 importers: @@ -89,7 +89,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + version: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) dogfood/ai/workbench: dependencies: @@ -120,22 +120,22 @@ importers: devDependencies: '@tailwindcss/vite': specifier: 'catalog:' - version: 4.3.3(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + version: 4.3.3(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) '@vitejs/plugin-react': specifier: ^6.0.3 - version: 6.1.2(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + version: 6.1.2(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) laravel-vite-plugin: specifier: 'catalog:' - version: 3.2.0(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + version: 3.2.0(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) tailwindcss: specifier: 'catalog:' version: 4.3.3 vite: specifier: 'catalog:' - version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + version: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vite-plus: specifier: 'catalog:' - version: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + version: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) src/foundation/resources/exceptions/renderer: dependencies: @@ -163,13 +163,13 @@ importers: devDependencies: '@tailwindcss/vite': specifier: 'catalog:' - version: 4.3.3(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + version: 4.3.3(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) vite: specifier: 'catalog:' - version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + version: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vite-plus: specifier: 'catalog:' - version: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + version: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) src/horizon: devDependencies: @@ -178,7 +178,7 @@ importers: version: 2.11.8 '@vitejs/plugin-vue': specifier: 'catalog:' - version: 6.0.10(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vue@3.5.44(typescript@7.0.2)) + version: 6.0.10(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vue@3.5.44(typescript@7.0.2)) '@vue/compiler-dom': specifier: 'catalog:' version: 3.5.44 @@ -214,10 +214,10 @@ importers: version: 1.106.0 vite: specifier: 'catalog:' - version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + version: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vite-plus: specifier: 'catalog:' - version: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + version: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) vue: specifier: 'catalog:' version: 3.5.44(typescript@7.0.2) @@ -235,7 +235,7 @@ importers: version: 2.11.8 '@vitejs/plugin-vue': specifier: 'catalog:' - version: 6.0.10(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vue@3.5.44(typescript@7.0.2)) + version: 6.0.10(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vue@3.5.44(typescript@7.0.2)) '@vue/compiler-dom': specifier: 'catalog:' version: 3.5.44 @@ -274,10 +274,10 @@ importers: version: 15.9.0 vite: specifier: 'catalog:' - version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + version: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vite-plus: specifier: 'catalog:' - version: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + version: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) vue: specifier: 'catalog:' version: 3.5.44(typescript@7.0.2) @@ -301,7 +301,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + version: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) src/workbench: devDependencies: @@ -316,7 +316,7 @@ importers: version: 10.6.1(postcss@8.5.31) laravel-vite-plugin: specifier: 'catalog:' - version: 3.2.0(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + version: 3.2.0(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) postcss: specifier: ^8.5.28 version: 8.5.31 @@ -325,10 +325,10 @@ importers: version: 3.4.19 vite: specifier: 'catalog:' - version: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + version: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vite-plus: specifier: 'catalog:' - version: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + version: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) packages: @@ -470,131 +470,131 @@ packages: '@one-ini/wasm@0.2.1': resolution: {integrity: sha512-TUqERXGNTifZ9y2g3wPxQrw3HpHv/02DsW3D90T9x0hhonrL1ZqpSmNrU2XkoIq0fP1N6gZfVQzy2Fw1ZvGBNg==} - '@oxc-project/runtime@0.154.0': - resolution: {integrity: sha512-cBQNGoh659BNgvA8OC/e4BNDtFkv03dJaYdkp2adgOQNtISNAPQ+nB4S8XvWtNk7Xl+0g+7aa+iRGR25Xycd1Q==} + '@oxc-project/runtime@0.155.0': + resolution: {integrity: sha512-VbW9raczGS5ZZ1FMunUBYHe4uV/R684qlQCoMdE3n5JT1PcqQsvmFpMYkF8AD+W04fBLGAq6Tr7YsTk0jjvDBA==} engines: {node: ^20.19.0 || >=22.12.0} - '@oxc-project/types@0.154.0': - resolution: {integrity: sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==} + '@oxc-project/types@0.155.0': + resolution: {integrity: sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q==} - '@oxfmt/binding-android-arm-eabi@0.73.0': - resolution: {integrity: sha512-dhunAdMbi/4o0Y66/+Wl6rpsNqpvE4i26Qw6bB77A97L2lJoktWg6m+X9QOon8iOEEqzD9rEZqFi5uZ4oIzhCA==} + '@oxfmt/binding-android-arm-eabi@0.74.0': + resolution: {integrity: sha512-xxQwrf5EPI5zxvuCTW8r6tzCTjPQ+h6+HqJ0iRKRy2zS1yBllt/xLVjoIyUc+FnR7g0a5g1SZv2yyDYhTItLbA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.73.0': - resolution: {integrity: sha512-dBoypjC2/xeyKyIhN2waX0/XeoccH9k0lVVe+sGRc4X8HSJVRtqyZDoXdr7bGY9A75c51dX0z0dLDpGQKwCdCg==} + '@oxfmt/binding-android-arm64@0.74.0': + resolution: {integrity: sha512-FJW3sATVvrWbV64al8xofCPZj4500Z/1W0ad7Tg1rcCUyh0EMbuSrsf3jsZS7CEbwQ5fQ0mRhmkc129GFgofSQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.73.0': - resolution: {integrity: sha512-Tn7Y+qWIpCQK+qdYwBcn6YvqeBuPiuG3tcNlYg16a5QL2o3rSmrgBB1nCyYqo0uvc1LNrfQW9/CK2tzFuID/Fg==} + '@oxfmt/binding-darwin-arm64@0.74.0': + resolution: {integrity: sha512-TfmNKBD30dGyMu9GCLiaw2rt/z7fms9LS+5cKcNOyWst0QgAs8oZs6wPxMbVaaDVE0/PCdCosgWM1vbu6f5NUg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.73.0': - resolution: {integrity: sha512-yVb0E0tg/+9Xc9YJCBq66qF1DC/aN5mhb5nnHz0k4ikwkMANtSul2VPDSwjYdXR6/DFimc+pPUgLBqkIa5Ctuw==} + '@oxfmt/binding-darwin-x64@0.74.0': + resolution: {integrity: sha512-5Usf8+DfC4Q3o/MD8FkN47GrI6y0KbhQ4KhZ2HGzMZcXjcW1AeldwbUn51aKih+1TVuJFF9qkNm6X1nzm6NYng==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.73.0': - resolution: {integrity: sha512-IKR4lFnBkYNL9Uqdxa5GMTQLvnLj+3sDwTvh2atHCzah0xeo+1xR5bigRU8uyXlOsA4VpXUn0UAimq4E4RBGmQ==} + '@oxfmt/binding-freebsd-x64@0.74.0': + resolution: {integrity: sha512-eL+1O3gykw1XihbZh2MRAciQvU2988LByrnjhyo/dmNG74X4oZ7XzigRoWPRFLfJCp8hzRkp/LbEhwe7uuT2eA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.73.0': - resolution: {integrity: sha512-7qnZ4g0ZCO3aZevOOQpZIABYwwXJW1lLDo2elviGdPQ0i11255xKEMFsi97fC1/nauQFseCJfELUy/k2JoOP+g==} + '@oxfmt/binding-linux-arm-gnueabihf@0.74.0': + resolution: {integrity: sha512-Xt0QtO2hJ4stnul8IL4LJGVLyp0xnc6ccN9fZ30yuubv8FWSRYxWwd1Za62KsEqAfmPSazoS/cLSUUJpja6npA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.73.0': - resolution: {integrity: sha512-36eBico2j/WPB84gb4FYd/jjqGCKOwU4M+TWLBUIqlnzTozQueLy0joA5BwsSp2OZFVkOlpCwvA3ndPamdiyLw==} + '@oxfmt/binding-linux-arm-musleabihf@0.74.0': + resolution: {integrity: sha512-/4wYQ0gZl4vUR0QoBTGaay73FmQ1GAjUa9z9101TR2xbGNrg4a+grazlR0yzEOe3/E+PJr5BGfj66MMzm/54vg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.73.0': - resolution: {integrity: sha512-VZ3cSgCAT18knKo52DcACmAuIoqG6qVEyeZRFbJd0xmYQ8GyYjuBc6IAh6MZHijT6q7p4/HPI+6ZWsCuhdV6iw==} + '@oxfmt/binding-linux-arm64-gnu@0.74.0': + resolution: {integrity: sha512-846bKzF+HJd+JkOFh3rxzmYvSq4Cm+GMT+p+mW7S13ZYo6GAAGv8bL8cAOi/XcdeMqxEJDCIMTVQLVBXHMCdig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.73.0': - resolution: {integrity: sha512-8YVzH59XSHcqwXNgc5qK851zCqwpmBgOifARXEPPe5/xTsIbHceYoPZAyqfpYV0CfKmzSaEDBgYHFza4rhx+Zw==} + '@oxfmt/binding-linux-arm64-musl@0.74.0': + resolution: {integrity: sha512-nI5FJwZ4Flo6CBdscpwxymZXxO3m3IErQJX55ZS6h+ObfGXu8FostL85L/RWPFaCLnIVE4/WfLw+2q8XvVEeUA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.73.0': - resolution: {integrity: sha512-MqRJnmCys0rwOgpWyEidSA7v/bvOqfNDJhPkQoR9mPVYrvSmMaW2Xhdpl28cbTA+brOM1is62GjIvJplVlUVpw==} + '@oxfmt/binding-linux-ppc64-gnu@0.74.0': + resolution: {integrity: sha512-BoQYKuqUdsKAFAkK6DLBvkdv4+t0hppSm79sB0wIcE2o/eOwNj+6Z3OlKY4q3Eta/tlssP65FaS9vHn8TeF7vQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.73.0': - resolution: {integrity: sha512-4t2F3SWJSK/O/kRRFnL0elJzn75fJGGu71gs1HtiWcS80SmJOpkHiwwLnTp9suGdns8VHEPxYS0GTcyVwHKWBA==} + '@oxfmt/binding-linux-riscv64-gnu@0.74.0': + resolution: {integrity: sha512-FSroRrM12+jCdSQklF9aOxsKpI6wFs4ZCo/1cSYG80RWZS6z4LV+l9tXRMT8lDGu+yohIZBwi9M76FzGPFDE2A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.73.0': - resolution: {integrity: sha512-D8b4I8I1kLdJQFwdFXNV+cwh3xWSiNKofjdGUk0nDpc0AHOds8T+poGbP9RWQMvgsvMb9OMgp/aV7ahBR37TbQ==} + '@oxfmt/binding-linux-riscv64-musl@0.74.0': + resolution: {integrity: sha512-0VLQD0dhGBLY9Ik/w+otMvD81D0YXi4VDwql65macV+O9UzmSWmUr6CKf+dMtysDeC0PnG3y6kBkRO08BJx52g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.73.0': - resolution: {integrity: sha512-QrC1S5VL1+N5rvorB15jH7CPvjVHA7GyUI5oVEd8Bd9m5605RDBQYTxDQ920DuylRoZOh2V7/xXpfCtFUrojUQ==} + '@oxfmt/binding-linux-s390x-gnu@0.74.0': + resolution: {integrity: sha512-6X7BbZNz34+OHM6Oqjf6TRcUyRn6aRcB4VtIDZAF/uQRyeVhg+G2hv6MlMvmnRekVSyTJ/8rG/u8W2dRFW6glg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.73.0': - resolution: {integrity: sha512-dFcUKQPltCEPa+YlkF3zqufn6nJPZdG73LpnjBvZFFsU3laCMssDXSYB99mVaj43FqdEgjpz5TO6gbYXcczd9g==} + '@oxfmt/binding-linux-x64-gnu@0.74.0': + resolution: {integrity: sha512-4kYIQK53yJXrxwxSzx8H2bojlf6/KezLGYLRXzV9QCCkoiCsvBTCid9Bg83dUPTLPa3g9K0730f6MKDbck1+OA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.73.0': - resolution: {integrity: sha512-LmQe+nV8e+eUwfPNCxDF+owbsg88toBiR/8rZRgVOeKQP3MaGefXZRvBoc1UG37iWEQ/9JlCXG9Mc4nf4N3agw==} + '@oxfmt/binding-linux-x64-musl@0.74.0': + resolution: {integrity: sha512-MfWz4wDREjp/JQDmuxtKieix4FHGa1z5rBvB3betdcPwZvf5fqeQrlCm1mmoyuUVQOh9Pt3M61HwN94kJCxw7Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.73.0': - resolution: {integrity: sha512-8weOr1OKknE0G2DbR6ejJUT9AOsoBU6cXlsetmbihHv1K+bK7qP61T6lJxY0k/aAO7GyOJiRGNlL+04r4eH8Xg==} + '@oxfmt/binding-openharmony-arm64@0.74.0': + resolution: {integrity: sha512-6rmAApkq5s1kbDjvNI6Nl5A+JqQkLORb2sR941pf5HWsrd01WGKyagK/B4vX9+kh1FfvRkNgEu59FHvVk4H8cQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.73.0': - resolution: {integrity: sha512-mZV+QxXYgb8E18gzAFItb937VIsE4dqWdStrD3tRRC52UubOYEuU+e7NaIqUsGdB5YsJqNeuck3Ue/af2r9wyA==} + '@oxfmt/binding-win32-arm64-msvc@0.74.0': + resolution: {integrity: sha512-+5SpRUnZNGeXTyumHnpHa791tQtt04qZs076WT8eTcGF2OJ631VLnf7S0kQSPuDG+PtJM0phadEB2WAr5n1w9g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.73.0': - resolution: {integrity: sha512-QJPUsbgUb97ZPPMkFUq6VQq0fLhC4Np19KL9aB/qKVwXnpKE6MIiILXqzhKnQ/UbnEDjKJ7Xv1d6zH4bYPCUdQ==} + '@oxfmt/binding-win32-ia32-msvc@0.74.0': + resolution: {integrity: sha512-1Fg+wYBO3Dsti1QX+/6EYHjeQzLC84tLOAqPu/r7ah3UJlUs9C/DB703Ay9Vcg+8VMzqZA2P5VGhzJraVUD4OQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.73.0': - resolution: {integrity: sha512-cGcze2fu+ykIaFtkEvZlkZ3HvnCLk+WISthtq99zWTWn0x13t5L9TxvGdLZMUMvtfYR+ICkiCwKnguVubdcw0w==} + '@oxfmt/binding-win32-x64-msvc@0.74.0': + resolution: {integrity: sha512-y9jXUgPBm2TZIWbQhMYRRYAQM57+r4fbykgc+j24ICIIZl+pcuoNhnq1CRGm3oFiHnEUnZO00qOtSPGQdJ2ziQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -629,130 +629,130 @@ packages: cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.88.0': - resolution: {integrity: sha512-k6Fgkf86ubfeH4GvqYsRolfuVAp0pfYVZvifSlXNHPdMaWUOfxUrmLEeDD7hF6U2oJuhsVxhSWq86/xCJ+9MmA==} + '@oxlint/binding-android-arm-eabi@1.89.0': + resolution: {integrity: sha512-Pflpba5F3gRAQ12sWglRxvWTHEdAusNDvmi4Y5N1T5SY1AGkM7AixOnET8qkrnLJ/F32qAlNokO6eCknT8tmnw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.88.0': - resolution: {integrity: sha512-AsIyhl9o1ejwtac7NMbhKpMdSml6NWR0ljKjZERxC8OHYDAW8o/Om9n1QHVLi4VCEUo42Zte2/Op9kyoGpJZMg==} + '@oxlint/binding-android-arm64@1.89.0': + resolution: {integrity: sha512-dYmyD1x7LaPlb79vZWUcpTShk8dI9z1I+IubtqrBxWNJaDhQdPHwcsvg7q5StTU4xWyd9AwPfLaTYFZY8NZgtQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.88.0': - resolution: {integrity: sha512-qgBjB7eEKcCB4RqjXGuoSZq74g0wD09fWFJ+HWcCJcKaga8TeUHtb+BY0SsxdleSLBgx4RZ7qmgo0EzqEq7JoQ==} + '@oxlint/binding-darwin-arm64@1.89.0': + resolution: {integrity: sha512-Z/oxOPiIqiS7fItRdxaaOEC+6GD3OpGi0IxoR0dMefzQHolsKLzScw2DNcu/5gUPok3tv+G9mslvYB5bo3i0qg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.88.0': - resolution: {integrity: sha512-sCz3X5Mw/xdzEzn4fsCt1Iq8NKJnGNfhR+b9B5k+4DysYAHsmIhYoocnBPiQ9kvY3+pUun2A0Z8Vtun/Nv6YoQ==} + '@oxlint/binding-darwin-x64@1.89.0': + resolution: {integrity: sha512-eAjTkHSjAxrdCPJxO6y5sPQL5pYLKb2kvxZz9UU7tXnOZk9I/HLtkvx9S7GHY883k+AmL/eC7mqFt+w7WCwk0w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.88.0': - resolution: {integrity: sha512-xTOdYOIqCufgIj0knvxu86FBfovP6QBiD7ngNva01gb8xPQCD5gXkE5APGczH35zStim1O3zqvFbLQmY85xXRg==} + '@oxlint/binding-freebsd-x64@1.89.0': + resolution: {integrity: sha512-0lNCA9Tl1n1FKpqy2/9ckp0kIuSrtJ/l9bMRgaEhOtM7TsOHshFuwOcvW8oA9teXLMOJT61LjEKWiaHDFGxF/w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.88.0': - resolution: {integrity: sha512-BLadtw88NdYJBrAtC1dYzrPfSP2d5zgx6iuZycdD0YmZBJ2Nqbit9Z4PSaIb6gxK25dweArGMPbOmq/0ugwUPg==} + '@oxlint/binding-linux-arm-gnueabihf@1.89.0': + resolution: {integrity: sha512-OTFEvi9W+JczaZD1Caj17SGCoW8H7TE+fvx0D7ATEWa1Lohn91UMxFQ6r145SiGy/yCxeIwptMys/RAM7gSVSQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.88.0': - resolution: {integrity: sha512-wILSczl/DaZsCcf7j7tvNbssp8hxEHIJxrv4j7+JjI3TAL1U/JTWbeUtoJJC/vo8+2hkTmHLYoKJjmiNbijdXw==} + '@oxlint/binding-linux-arm-musleabihf@1.89.0': + resolution: {integrity: sha512-fRxgNHvBLswj6MrhW2uBq5UDhPRQlCbHeq/Nf8yuG9zOxhDsXqX/mi2PX1axgsQxMm88el7HLiJAzglAnflYxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.88.0': - resolution: {integrity: sha512-jutT2Hy8JCuZuMwIFHo2HtyxfOfBJHyQxlBHBK1SL9W2f8n/WhjAP0FnDMz9d/mjhv9+QA0mx5AdQ9fDjsrFFg==} + '@oxlint/binding-linux-arm64-gnu@1.89.0': + resolution: {integrity: sha512-v9ENzHvwrQoYSWTtviH1pOYc7R9UovDxVofnck50cNmPihRRTcrg5X0UlRbLOZ7IVtxPOlh2hb7kDO+FpE8SfQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.88.0': - resolution: {integrity: sha512-8N8iGV9hh4Lkeuq7QsEkjoXNRRflqR1G2qyoGYXcA0k5qllrdqmfylKEXyHIVERxqNDyMO4StqDQ0uLxXcBQAg==} + '@oxlint/binding-linux-arm64-musl@1.89.0': + resolution: {integrity: sha512-vfffe8mQ484AzVadlUOFpgL+JDwhvs9hrzxTgQP1FVfi4ZEUUYeffXlzC9dFV7Dgnu5s/KrNKvfmG6HxBd/ISQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.88.0': - resolution: {integrity: sha512-5ydcQt3znNQ/rGEWYRvpS2cK9+T18P2VYgnyJ+eloEAwi9YUmsXy5VGrRqNWIK6qzWwHGnEE3ojVqqQNo+f0hA==} + '@oxlint/binding-linux-ppc64-gnu@1.89.0': + resolution: {integrity: sha512-Buonlp4hVS7RGNP5M7bPpGNQrenyzA2C64JcO/oz9C1c6dGOXJ7VEAnaiAMY7voou0JYyCJdvDVlFQc5DZX3DQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.88.0': - resolution: {integrity: sha512-iGx0uvFT54b/5DPJdEBCxYIrVB3v0uTy+JhRh3CyEp8/22BXDkG8rffkFwREdLsdj9mOrPbJYVOZw8ipcBuzjw==} + '@oxlint/binding-linux-riscv64-gnu@1.89.0': + resolution: {integrity: sha512-1eVwJJrJ3lYIPT+cm7BcBBZZXY6IQZP/oyonaruTor/MrVyWOpvnwt1oD5CLdsHg5aVBIxXodQSvt+1gK6ck5w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.88.0': - resolution: {integrity: sha512-Wt47mKMWelVjRjuR7BCFxXkB0m79K1sTEbqMHRWciKUgo5iv7sWsF4RXSOyFYHD9zerNrkRZblnN273rhwOA1Q==} + '@oxlint/binding-linux-riscv64-musl@1.89.0': + resolution: {integrity: sha512-fRQQsKUGShdEL5Fl9o9uhj3grkZbeXKLv0icQ5tspUCNYSDy8wzzEcP1WC2bKdTQ0rSszbKlO3iiHORxV22DLg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.88.0': - resolution: {integrity: sha512-g3L12KCS4+1gtUt3RbJ4F+Yxx4LNmmZLEPfhd8rAHEJjJk2lAgNfnjGoP/phJyhG5WS6VbNYbJtadJelNfvTMA==} + '@oxlint/binding-linux-s390x-gnu@1.89.0': + resolution: {integrity: sha512-dEtYjIy6+fHcjKklsmtmMkZRhwHr2UbzjmpvuPtQYSeN9awQ2hx5JLms5Wvri+8P/NZtD8v5hfgHALUql+/Ycg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.88.0': - resolution: {integrity: sha512-og/XHVDOD9ZJgRiZkT9DgzQd4sr+JZW7e4l17Q/jVMZiIbeEd4uIu9iAoUbTF1gIvVZoRc4QDOxyVUvQGp4WHg==} + '@oxlint/binding-linux-x64-gnu@1.89.0': + resolution: {integrity: sha512-hqjwIj5KOYM8Wjef/p7IruNi1iByLhSzbMBk62y22P/E8q7Jj0Pp+DeUTKTRYyZYCDtU1wEGlnWOnttHbyNIww==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.88.0': - resolution: {integrity: sha512-EmhBsp+xuKb6YbZNEMLBo80Xw71AZOIm18do8ni/GLX/Lw1XzNhVYpbxO1KasYtoAvrGtCOdCPULRlIE0bPbTQ==} + '@oxlint/binding-linux-x64-musl@1.89.0': + resolution: {integrity: sha512-iAQN3e6o9UgUCcdGwqRx/jYcKbG3HK7ur3rH6lTqVcQeNGZ85AeWIg6cgn/edwFQ0ofEZZItMwkzyCVTahnvEA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.88.0': - resolution: {integrity: sha512-DBUcx/KwYdg1DAz+G01P/GFv512J04EsZadbHptx2FTpWT/MWRg21ZToNHt9pnsfQRBQ3et/g+vqzbvrdAVbuA==} + '@oxlint/binding-openharmony-arm64@1.89.0': + resolution: {integrity: sha512-MNxjEByz3N/7Q4qFQuPWvc14TpN9LoD3pECEDsJTZbP/wyB7U+hduUXEl7QAbbgVezFwsb9sWtNUO6XVBndOhg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.88.0': - resolution: {integrity: sha512-xeVd0n4XG5HvynxfYvXi6uB+JvZbVZataowPS8cYpMbYg9mwE4sSyElFQd4LVJFifq8bRLDSR18XgEildmcXAg==} + '@oxlint/binding-win32-arm64-msvc@1.89.0': + resolution: {integrity: sha512-xxe+i9wfuFso/VnoL4mfj0UlzE+cfCFiUB06vkXCMyQdgVVmItF5FfAF4tATAktSU6uHruWR+FjqvAT9cHZZzg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.88.0': - resolution: {integrity: sha512-MA6iOw//amleaz2mVMsZrA7NHlEexjTrhIxSMFCXQi6IUuQO+3Od00GZhr2iCYw6RFzMW36jkKK/WHBXMsoQAw==} + '@oxlint/binding-win32-ia32-msvc@1.89.0': + resolution: {integrity: sha512-saFy07cO94E664glwgLmKLI40HgTL1PY86y4T6+ud4niX4H0Kdqjcmqmjps7mQNdBytV5guGbXDdyQtRfeLbLA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.88.0': - resolution: {integrity: sha512-3Sz6rVWe8Dy+ufsXurFN1GX2dX43F/lQMSLelK9gFjiba7nHvLlqlhR5cHlWVnCpSqUY1hN9pyCX9XToe6ve6g==} + '@oxlint/binding-win32-x64-msvc@1.89.0': + resolution: {integrity: sha512-hLFJ/o0f/dTJh/EW0vmcbREYSQO9tYgfiAAlsOkiLFiJuRRWvYtFELUXN5o2ijpSo/tlO1n+YlWN58tZwPG18A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint/plugins@1.88.0': - resolution: {integrity: sha512-G2xbfWTrbqYRhR/vfeKNMQf/sS3/ti0iKJAAkRxHuXWZH/hne/dyIb9QKk5LuR6cJLARxsL6FMoAYmfspd74MQ==} + '@oxlint/plugins@1.89.0': + resolution: {integrity: sha512-TJBBNl4KIxbxrSk7gsqlz6iM1kxidfyTizEzfWqXvijF2Ou8/Da58GvjemBVmCmaUamwRij+JwsVc0kpBFuqfg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} '@parcel/watcher-android-arm64@2.6.0': @@ -1867,12 +1867,9 @@ packages: resolution: {integrity: sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg==} peerDependencies: msw: ^2.4.9 || ^3.0.0 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true - vite: - optional: true '@vitest/pretty-format@5.0.5': resolution: {integrity: sha512-24sSdIlMA9DVPXnYiB9siwU0bTd887Y9jX3+vabztCSsvHwo7ipvJ/a2L3Rljw8+2rK1f5fCj3h5U9I/Jsqgbw==} @@ -1891,14 +1888,14 @@ packages: '@vitest/utils@5.0.5': resolution: {integrity: sha512-/rBu7gzLDQI/SC79Tn98b5DwOgaHU/Gt4I9OVeP3YofzNaYtbImRCOJQmT1S6vOJg9gEIlWC+r3QxKkjuSa1nQ==} - '@voidzero-dev/vite-plus-core@1.3.0': - resolution: {integrity: sha512-Bk/LBwNrxoB8XbGUtGJE3r+U9P4puvdZ6UH36ekygE1J+on/ytwM/yBgQwBnvmX6/MVax+2xxj8wk0N/KkyNeA==} + '@voidzero-dev/vite-plus-core@1.4.0': + resolution: {integrity: sha512-LTGXCdZ58qJKfuCOmf0hnZOVA64eOgzDQOqYpI+71tPxk/0cGqPQhYDGQQZoEvNLAn1G2Lo4gKadsQdWxb99WQ==} engines: {node: ^20.19.0 || ^22.18.0 || >=24.11.0} peerDependencies: '@arethetypeswrong/core': ^0.18.1 '@types/node': ^20.19.0 || >=22.12.0 '@typescript/native-preview': '*' - '@vitejs/devtools': ^0.7.1 + '@vitejs/devtools': ^0.7.1 || ^0.8.0 '@volar/typescript': ~2.4.0 '@vue/language-core': ~3.2.0 || ~3.3.0 esbuild: ^0.27.0 || ^0.28.0 @@ -1960,54 +1957,54 @@ packages: yaml: optional: true - '@voidzero-dev/vite-plus-darwin-arm64@1.3.0': - resolution: {integrity: sha512-BF4W+UwGxUkHPrH2pZLhafJfkJT20WZIbMzZgZLGQ44LPpZPX8Sb+qtblUnXUkkCzTqad4XtSPgiLKglvZkNQQ==} + '@voidzero-dev/vite-plus-darwin-arm64@1.4.0': + resolution: {integrity: sha512-5Ie3vnrFrF5GFU+0VhbLuyoSmnzd8m42BvbLjncMOmAb6D1sEq4g37dlxPiVcJEqITM0t4zFnLkNSF4YAlk0FQ==} engines: {node: '>=20.0.0'} cpu: [arm64] os: [darwin] - '@voidzero-dev/vite-plus-darwin-x64@1.3.0': - resolution: {integrity: sha512-vLZTk0r+s3e7mycj4SGBbcpK+xyM8a86hXQZtQbJyW7xrGJzsavISy7/m4gGZpWN32Hl4Tmx9V/ffeaoXLkRdg==} + '@voidzero-dev/vite-plus-darwin-x64@1.4.0': + resolution: {integrity: sha512-vaR7F+ix/91EycJYXOQ24Nzw470U12x3L+YLbbtuRwHDMW67fCbhXzcW+otZS8q4eRdIzEcA5svv9P8kcsaRkA==} engines: {node: '>=20.0.0'} cpu: [x64] os: [darwin] - '@voidzero-dev/vite-plus-linux-arm64-gnu@1.3.0': - resolution: {integrity: sha512-H43Dj9RkEyjOFXxTk3DLTa6hqmHhxUN9vbbqNdwQoc3/TPNhC1zsLZy3qgUFbYGLdS7M9jICCqFzj5GDSYrhIw==} + '@voidzero-dev/vite-plus-linux-arm64-gnu@1.4.0': + resolution: {integrity: sha512-58/PZrp+f3Hy6kFVKqbrZVenjo5t012rTOgRSgMoJyOhJjXBHqvORh9lB9GIip9r9maoWN31R1a44obPX9MD7g==} engines: {node: '>=20.0.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@voidzero-dev/vite-plus-linux-arm64-musl@1.3.0': - resolution: {integrity: sha512-Se2GlvzUZOvFy+5hoQHoG7LFeiplKs+EJwaskayCSLwZAP+e6/LTK7EK+8OcdMpmdsLPtZoTJHkIjSzrQowJwQ==} + '@voidzero-dev/vite-plus-linux-arm64-musl@1.4.0': + resolution: {integrity: sha512-Ij08vPrxG1J23wQVm0LtLPrQevv4tos8TirP/jGTpQQmQAgYOpmIfMrx8BF+vOGMncOIo+kKacp4jPVmhctfhg==} engines: {node: '>=20.0.0'} cpu: [arm64] os: [linux] libc: [musl] - '@voidzero-dev/vite-plus-linux-x64-gnu@1.3.0': - resolution: {integrity: sha512-moxojk6zmog2SaLE4/cG75acE6X3cjR0/52Ba8OG2ct0r+dv55VSE0kellAuxS6o9VHy7TBhkIpwpFpQ34rnAA==} + '@voidzero-dev/vite-plus-linux-x64-gnu@1.4.0': + resolution: {integrity: sha512-RbM21Y5cSx5WesaFV/Swo22JRvimjO6NDItoL5wNI6NTa987ck7v19HBmp+V4nkOCFHbMdxwfhyALDw0y06xEA==} engines: {node: '>=20.0.0'} cpu: [x64] os: [linux] libc: [glibc] - '@voidzero-dev/vite-plus-linux-x64-musl@1.3.0': - resolution: {integrity: sha512-CykxH008Dy9u20Myki9/UIqq3Dbo1ZMqa098aS6AtWp3QKyEKzSLPB3jKDUklgmu83xiFe5BpPA+33oF0yNY7A==} + '@voidzero-dev/vite-plus-linux-x64-musl@1.4.0': + resolution: {integrity: sha512-Q+p+YJztEmqjJoumTmPaBQ/PEx3eFhiNTQTn5+85gkBUiRexkFQ8A86TMFxbKVgrJbiAOltkrJRmVRhRs5Q0sA==} engines: {node: '>=20.0.0'} cpu: [x64] os: [linux] libc: [musl] - '@voidzero-dev/vite-plus-win32-arm64-msvc@1.3.0': - resolution: {integrity: sha512-l61S9b8aeiDpi8wPRfz4HonqRkLpt/OVYs0sCskvlXYSW/L8qfLOtuWH/xpF8xDjMIfBefKejBVfU1UlRax5Tw==} + '@voidzero-dev/vite-plus-win32-arm64-msvc@1.4.0': + resolution: {integrity: sha512-PLGVov8c4/QfveOM/AYjYDlgMCQ0SxCmJp392qBgxaSXP0nEg6yP4hn5UtnhxvluhHEzVkTf2r1Q3LNlZRI7kw==} engines: {node: '>=20.0.0'} cpu: [arm64] os: [win32] - '@voidzero-dev/vite-plus-win32-x64-msvc@1.3.0': - resolution: {integrity: sha512-b6Ng8YezA6JA3adQBNA+8Mn26+OX4fw/tsPfnk//5Cx+1XFDZoAxWYXtGzc0yUQPEKnoGtfw/0OUvcibg61yYg==} + '@voidzero-dev/vite-plus-win32-x64-msvc@1.4.0': + resolution: {integrity: sha512-DYG7wDsMLLs2/+8qfeyLScdoMh+aPM4IZanoVPSNt8fMeeXR2GQiiZDKc0qZHKN9+YcdCGK1HLAdMAGSywKvsA==} engines: {node: '>=20.0.0'} cpu: [x64] os: [win32] @@ -2904,8 +2901,8 @@ packages: oniguruma-to-es@4.3.6: resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} - oxfmt@0.73.0: - resolution: {integrity: sha512-Ke4xbpuzqIBP4bkd48q815RhqWNJqOBzxVA9199M4qcF0y0LtpUv7UTg1dJ5ea6jw0RBUDZvX3qOUMNmmxfxbw==} + oxfmt@0.74.0: + resolution: {integrity: sha512-G4OP7FgwGYZR5ttRpCOOfJdyRsSyvtn34vSXnjDq6Oqed5Dufg5mB44mank+51nAdHOKuTJ0lj0mksY7L+N61A==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2924,8 +2921,8 @@ packages: resolution: {integrity: sha512-vQ9WDF3fiZJvRdSDosNOflerOCOpwol6vWzVDxW76RcU19cjTh7engvkHLvmv+tzedR8fAiJUVUGr6IVZdkzXQ==} hasBin: true - oxlint@1.88.0: - resolution: {integrity: sha512-uIcFgw8BmqvFRGzH5+r7d2zJMEexwNSrEmM6WQNnGh3VkO0XYTEFoyxyNE6tjKMUib9Cue4XJA2DoWruO5D6aw==} + oxlint@1.89.0: + resolution: {integrity: sha512-a25hkfo61okGPvJz432fRIPoxlKtNnOVy9w4rzrmGgNm4iVTPwpSQlmdqNWnp6p9QyyPVfAZRgnf5XvSBZFk/A==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -3352,8 +3349,8 @@ packages: vite-plugin-full-reload@1.2.0: resolution: {integrity: sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==} - vite-plus@1.3.0: - resolution: {integrity: sha512-wZGsDeYfPcrCegqgY68pJJHGTpEZdsZvIMH2/mWXkGkBD72UY3M7z24Sv3ZeOL/FrqbbtyqBOfkKO6BvoQ8wMw==} + vite-plus@1.4.0: + resolution: {integrity: sha512-bLyUKNFl0XjbHe20FpDM6OuknOb2E4ueoWKCW4n+wMO2+lQtA9tmAjW+Xs0Xe9rg2+tCgY3W13GOgDh2wjwjAQ==} engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} hasBin: true peerDependencies: @@ -3613,65 +3610,65 @@ snapshots: '@one-ini/wasm@0.2.1': {} - '@oxc-project/runtime@0.154.0': {} + '@oxc-project/runtime@0.155.0': {} - '@oxc-project/types@0.154.0': {} + '@oxc-project/types@0.155.0': {} - '@oxfmt/binding-android-arm-eabi@0.73.0': + '@oxfmt/binding-android-arm-eabi@0.74.0': optional: true - '@oxfmt/binding-android-arm64@0.73.0': + '@oxfmt/binding-android-arm64@0.74.0': optional: true - '@oxfmt/binding-darwin-arm64@0.73.0': + '@oxfmt/binding-darwin-arm64@0.74.0': optional: true - '@oxfmt/binding-darwin-x64@0.73.0': + '@oxfmt/binding-darwin-x64@0.74.0': optional: true - '@oxfmt/binding-freebsd-x64@0.73.0': + '@oxfmt/binding-freebsd-x64@0.74.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.73.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.74.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.73.0': + '@oxfmt/binding-linux-arm-musleabihf@0.74.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.73.0': + '@oxfmt/binding-linux-arm64-gnu@0.74.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.73.0': + '@oxfmt/binding-linux-arm64-musl@0.74.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.73.0': + '@oxfmt/binding-linux-ppc64-gnu@0.74.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.73.0': + '@oxfmt/binding-linux-riscv64-gnu@0.74.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.73.0': + '@oxfmt/binding-linux-riscv64-musl@0.74.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.73.0': + '@oxfmt/binding-linux-s390x-gnu@0.74.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.73.0': + '@oxfmt/binding-linux-x64-gnu@0.74.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.73.0': + '@oxfmt/binding-linux-x64-musl@0.74.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.73.0': + '@oxfmt/binding-openharmony-arm64@0.74.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.73.0': + '@oxfmt/binding-win32-arm64-msvc@0.74.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.73.0': + '@oxfmt/binding-win32-ia32-msvc@0.74.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.73.0': + '@oxfmt/binding-win32-x64-msvc@0.74.0': optional: true '@oxlint-tsgolint/darwin-arm64@7.0.2004': @@ -3692,64 +3689,64 @@ snapshots: '@oxlint-tsgolint/win32-x64@7.0.2004': optional: true - '@oxlint/binding-android-arm-eabi@1.88.0': + '@oxlint/binding-android-arm-eabi@1.89.0': optional: true - '@oxlint/binding-android-arm64@1.88.0': + '@oxlint/binding-android-arm64@1.89.0': optional: true - '@oxlint/binding-darwin-arm64@1.88.0': + '@oxlint/binding-darwin-arm64@1.89.0': optional: true - '@oxlint/binding-darwin-x64@1.88.0': + '@oxlint/binding-darwin-x64@1.89.0': optional: true - '@oxlint/binding-freebsd-x64@1.88.0': + '@oxlint/binding-freebsd-x64@1.89.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.88.0': + '@oxlint/binding-linux-arm-gnueabihf@1.89.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.88.0': + '@oxlint/binding-linux-arm-musleabihf@1.89.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.88.0': + '@oxlint/binding-linux-arm64-gnu@1.89.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.88.0': + '@oxlint/binding-linux-arm64-musl@1.89.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.88.0': + '@oxlint/binding-linux-ppc64-gnu@1.89.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.88.0': + '@oxlint/binding-linux-riscv64-gnu@1.89.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.88.0': + '@oxlint/binding-linux-riscv64-musl@1.89.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.88.0': + '@oxlint/binding-linux-s390x-gnu@1.89.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.88.0': + '@oxlint/binding-linux-x64-gnu@1.89.0': optional: true - '@oxlint/binding-linux-x64-musl@1.88.0': + '@oxlint/binding-linux-x64-musl@1.89.0': optional: true - '@oxlint/binding-openharmony-arm64@1.88.0': + '@oxlint/binding-openharmony-arm64@1.89.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.88.0': + '@oxlint/binding-win32-arm64-msvc@1.89.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.88.0': + '@oxlint/binding-win32-ia32-msvc@1.89.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.88.0': + '@oxlint/binding-win32-x64-msvc@1.89.0': optional: true - '@oxlint/plugins@1.88.0': {} + '@oxlint/plugins@1.89.0': {} '@parcel/watcher-android-arm64@2.6.0': optional: true @@ -4513,12 +4510,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - '@tailwindcss/vite@4.3.3(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))': + '@tailwindcss/vite@4.3.3(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))': dependencies: '@tailwindcss/node': 4.3.3 '@tailwindcss/oxide': 4.3.3 tailwindcss: 4.3.3 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' '@testing-library/dom@10.4.2': dependencies: @@ -4628,94 +4625,216 @@ snapshots: '@ungap/structured-clone@1.4.0': {} - '@vitejs/plugin-react@6.1.2(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))': + '@vitejs/plugin-react@6.1.2(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' - '@vitejs/plugin-vue@6.0.10(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vue@3.5.44(typescript@7.0.2))': + '@vitejs/plugin-vue@6.0.10(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vue@3.5.44(typescript@7.0.2))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vue: 3.5.44(typescript@7.0.2) - '@vitest/browser-preview@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5)': + '@vitest/browser-preview@5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5)': dependencies: '@testing-library/dom': 10.4.2 '@testing-library/user-event': 14.6.8(@testing-library/dom@10.4.2) - '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) - vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3) + '@vitest/browser': 5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) + vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' - bufferutil + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun - utf-8-validate - - vite + - vue-tsc + - yaml - '@vitest/browser-preview@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5)': + '@vitest/browser-preview@5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5)': dependencies: '@testing-library/dom': 10.4.2 '@testing-library/user-event': 14.6.8(@testing-library/dom@10.4.2) - '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) - vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3) + '@vitest/browser': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) + vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' - bufferutil + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun - utf-8-validate - - vite + - vue-tsc + - yaml - '@vitest/browser@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5)': + '@vitest/browser@5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5)': dependencies: '@blazediff/core': 1.10.0 - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + '@vitest/mocker': 5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) '@vitest/ui': 5.0.5(vitest@5.0.5) '@vitest/utils': 5.0.5 magic-string: 1.4.3 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.2.0 - vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3) + vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) ws: 8.22.0 transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' - bufferutil + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun - utf-8-validate - - vite + - vue-tsc + - yaml - '@vitest/browser@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5)': + '@vitest/browser@5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5)': dependencies: '@blazediff/core': 1.10.0 - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + '@vitest/mocker': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) '@vitest/ui': 5.0.5(vitest@5.0.5) '@vitest/utils': 5.0.5 magic-string: 1.4.3 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.2.0 - vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3) + vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) ws: 8.22.0 transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' - bufferutil + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun - utf-8-validate - - vite + - vue-tsc + - yaml - '@vitest/mocker@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))': + '@vitest/mocker@5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)': dependencies: '@jridgewell/trace-mapping': 0.3.31 '@vitest/spy': 5.0.5 estree-walker: 3.0.3 magic-string: 1.4.3 - optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' + - esbuild + - jiti + - less + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - vue-tsc + - yaml - '@vitest/mocker@5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))': + '@vitest/mocker@5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)': dependencies: '@jridgewell/trace-mapping': 0.3.31 '@vitest/spy': 5.0.5 estree-walker: 3.0.3 magic-string: 1.4.3 - optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' + - esbuild + - jiti + - less + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - vue-tsc + - yaml '@vitest/pretty-format@5.0.5': dependencies: @@ -4738,7 +4857,7 @@ snapshots: pathe: 2.0.3 sirv: 3.0.2 tinyrainbow: 3.2.0 - vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3) + vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) '@vitest/utils@5.0.5': dependencies: @@ -4746,74 +4865,74 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.2.0 - '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)': + '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)': dependencies: - '@oxc-project/runtime': 0.154.0 - '@oxc-project/types': 0.154.0 + '@oxc-project/runtime': 0.155.0 + '@oxc-project/types': 0.155.0 lightningcss: 1.33.0 postcss: 8.5.31 yuku-parser: 0.19.0 optionalDependencies: '@types/node': 22.20.5 - '@voidzero-dev/vite-plus-darwin-arm64': 1.3.0 - '@voidzero-dev/vite-plus-darwin-x64': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-musl': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-musl': 1.3.0 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.3.0 - '@voidzero-dev/vite-plus-win32-x64-msvc': 1.3.0 + '@voidzero-dev/vite-plus-darwin-arm64': 1.4.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.4.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.4.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.4.0 fsevents: 2.3.3 jiti: 1.21.7 sass: 1.106.0 terser: 5.51.2 typescript: 7.0.2 - '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)': + '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)': dependencies: - '@oxc-project/runtime': 0.154.0 - '@oxc-project/types': 0.154.0 + '@oxc-project/runtime': 0.155.0 + '@oxc-project/types': 0.155.0 lightningcss: 1.33.0 postcss: 8.5.31 yuku-parser: 0.19.0 optionalDependencies: '@types/node': 22.20.5 - '@voidzero-dev/vite-plus-darwin-arm64': 1.3.0 - '@voidzero-dev/vite-plus-darwin-x64': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-musl': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-musl': 1.3.0 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.3.0 - '@voidzero-dev/vite-plus-win32-x64-msvc': 1.3.0 + '@voidzero-dev/vite-plus-darwin-arm64': 1.4.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.4.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.4.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.4.0 fsevents: 2.3.3 jiti: 2.7.0 sass: 1.106.0 terser: 5.51.2 typescript: 7.0.2 - '@voidzero-dev/vite-plus-darwin-arm64@1.3.0': + '@voidzero-dev/vite-plus-darwin-arm64@1.4.0': optional: true - '@voidzero-dev/vite-plus-darwin-x64@1.3.0': + '@voidzero-dev/vite-plus-darwin-x64@1.4.0': optional: true - '@voidzero-dev/vite-plus-linux-arm64-gnu@1.3.0': + '@voidzero-dev/vite-plus-linux-arm64-gnu@1.4.0': optional: true - '@voidzero-dev/vite-plus-linux-arm64-musl@1.3.0': + '@voidzero-dev/vite-plus-linux-arm64-musl@1.4.0': optional: true - '@voidzero-dev/vite-plus-linux-x64-gnu@1.3.0': + '@voidzero-dev/vite-plus-linux-x64-gnu@1.4.0': optional: true - '@voidzero-dev/vite-plus-linux-x64-musl@1.3.0': + '@voidzero-dev/vite-plus-linux-x64-musl@1.4.0': optional: true - '@voidzero-dev/vite-plus-win32-arm64-msvc@1.3.0': + '@voidzero-dev/vite-plus-win32-arm64-msvc@1.4.0': optional: true - '@voidzero-dev/vite-plus-win32-x64-msvc@1.3.0': + '@voidzero-dev/vite-plus-win32-x64-msvc@1.4.0': optional: true '@vue/compiler-core@3.5.44': @@ -5394,18 +5513,18 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' - laravel-vite-plugin@3.2.0(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): + laravel-vite-plugin@3.2.0(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): dependencies: picocolors: 1.1.1 tinyglobby: 0.2.17 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vite-plugin-full-reload: 1.2.0 - laravel-vite-plugin@3.2.0(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): + laravel-vite-plugin@3.2.0(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): dependencies: picocolors: 1.1.1 tinyglobby: 0.2.17 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' vite-plugin-full-reload: 1.2.0 lightningcss-android-arm64@1.32.0: @@ -5634,55 +5753,55 @@ snapshots: regex: 6.1.0 regex-recursion: 6.0.2 - oxfmt@0.73.0(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): + oxfmt@0.74.0(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): dependencies: tinypool: 2.2.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.73.0 - '@oxfmt/binding-android-arm64': 0.73.0 - '@oxfmt/binding-darwin-arm64': 0.73.0 - '@oxfmt/binding-darwin-x64': 0.73.0 - '@oxfmt/binding-freebsd-x64': 0.73.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.73.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.73.0 - '@oxfmt/binding-linux-arm64-gnu': 0.73.0 - '@oxfmt/binding-linux-arm64-musl': 0.73.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.73.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.73.0 - '@oxfmt/binding-linux-riscv64-musl': 0.73.0 - '@oxfmt/binding-linux-s390x-gnu': 0.73.0 - '@oxfmt/binding-linux-x64-gnu': 0.73.0 - '@oxfmt/binding-linux-x64-musl': 0.73.0 - '@oxfmt/binding-openharmony-arm64': 0.73.0 - '@oxfmt/binding-win32-arm64-msvc': 0.73.0 - '@oxfmt/binding-win32-ia32-msvc': 0.73.0 - '@oxfmt/binding-win32-x64-msvc': 0.73.0 - vite-plus: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) - - oxfmt@0.73.0(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): + '@oxfmt/binding-android-arm-eabi': 0.74.0 + '@oxfmt/binding-android-arm64': 0.74.0 + '@oxfmt/binding-darwin-arm64': 0.74.0 + '@oxfmt/binding-darwin-x64': 0.74.0 + '@oxfmt/binding-freebsd-x64': 0.74.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.74.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.74.0 + '@oxfmt/binding-linux-arm64-gnu': 0.74.0 + '@oxfmt/binding-linux-arm64-musl': 0.74.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.74.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.74.0 + '@oxfmt/binding-linux-riscv64-musl': 0.74.0 + '@oxfmt/binding-linux-s390x-gnu': 0.74.0 + '@oxfmt/binding-linux-x64-gnu': 0.74.0 + '@oxfmt/binding-linux-x64-musl': 0.74.0 + '@oxfmt/binding-openharmony-arm64': 0.74.0 + '@oxfmt/binding-win32-arm64-msvc': 0.74.0 + '@oxfmt/binding-win32-ia32-msvc': 0.74.0 + '@oxfmt/binding-win32-x64-msvc': 0.74.0 + vite-plus: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + + oxfmt@0.74.0(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): dependencies: tinypool: 2.2.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.73.0 - '@oxfmt/binding-android-arm64': 0.73.0 - '@oxfmt/binding-darwin-arm64': 0.73.0 - '@oxfmt/binding-darwin-x64': 0.73.0 - '@oxfmt/binding-freebsd-x64': 0.73.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.73.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.73.0 - '@oxfmt/binding-linux-arm64-gnu': 0.73.0 - '@oxfmt/binding-linux-arm64-musl': 0.73.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.73.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.73.0 - '@oxfmt/binding-linux-riscv64-musl': 0.73.0 - '@oxfmt/binding-linux-s390x-gnu': 0.73.0 - '@oxfmt/binding-linux-x64-gnu': 0.73.0 - '@oxfmt/binding-linux-x64-musl': 0.73.0 - '@oxfmt/binding-openharmony-arm64': 0.73.0 - '@oxfmt/binding-win32-arm64-msvc': 0.73.0 - '@oxfmt/binding-win32-ia32-msvc': 0.73.0 - '@oxfmt/binding-win32-x64-msvc': 0.73.0 - vite-plus: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + '@oxfmt/binding-android-arm-eabi': 0.74.0 + '@oxfmt/binding-android-arm64': 0.74.0 + '@oxfmt/binding-darwin-arm64': 0.74.0 + '@oxfmt/binding-darwin-x64': 0.74.0 + '@oxfmt/binding-freebsd-x64': 0.74.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.74.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.74.0 + '@oxfmt/binding-linux-arm64-gnu': 0.74.0 + '@oxfmt/binding-linux-arm64-musl': 0.74.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.74.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.74.0 + '@oxfmt/binding-linux-riscv64-musl': 0.74.0 + '@oxfmt/binding-linux-s390x-gnu': 0.74.0 + '@oxfmt/binding-linux-x64-gnu': 0.74.0 + '@oxfmt/binding-linux-x64-musl': 0.74.0 + '@oxfmt/binding-openharmony-arm64': 0.74.0 + '@oxfmt/binding-win32-arm64-msvc': 0.74.0 + '@oxfmt/binding-win32-ia32-msvc': 0.74.0 + '@oxfmt/binding-win32-x64-msvc': 0.74.0 + vite-plus: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) oxlint-tsgolint@7.0.2004: optionalDependencies: @@ -5693,53 +5812,53 @@ snapshots: '@oxlint-tsgolint/win32-arm64': 7.0.2004 '@oxlint-tsgolint/win32-x64': 7.0.2004 - oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): + oxlint@1.89.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.88.0 - '@oxlint/binding-android-arm64': 1.88.0 - '@oxlint/binding-darwin-arm64': 1.88.0 - '@oxlint/binding-darwin-x64': 1.88.0 - '@oxlint/binding-freebsd-x64': 1.88.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.88.0 - '@oxlint/binding-linux-arm-musleabihf': 1.88.0 - '@oxlint/binding-linux-arm64-gnu': 1.88.0 - '@oxlint/binding-linux-arm64-musl': 1.88.0 - '@oxlint/binding-linux-ppc64-gnu': 1.88.0 - '@oxlint/binding-linux-riscv64-gnu': 1.88.0 - '@oxlint/binding-linux-riscv64-musl': 1.88.0 - '@oxlint/binding-linux-s390x-gnu': 1.88.0 - '@oxlint/binding-linux-x64-gnu': 1.88.0 - '@oxlint/binding-linux-x64-musl': 1.88.0 - '@oxlint/binding-openharmony-arm64': 1.88.0 - '@oxlint/binding-win32-arm64-msvc': 1.88.0 - '@oxlint/binding-win32-ia32-msvc': 1.88.0 - '@oxlint/binding-win32-x64-msvc': 1.88.0 + '@oxlint/binding-android-arm-eabi': 1.89.0 + '@oxlint/binding-android-arm64': 1.89.0 + '@oxlint/binding-darwin-arm64': 1.89.0 + '@oxlint/binding-darwin-x64': 1.89.0 + '@oxlint/binding-freebsd-x64': 1.89.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.89.0 + '@oxlint/binding-linux-arm-musleabihf': 1.89.0 + '@oxlint/binding-linux-arm64-gnu': 1.89.0 + '@oxlint/binding-linux-arm64-musl': 1.89.0 + '@oxlint/binding-linux-ppc64-gnu': 1.89.0 + '@oxlint/binding-linux-riscv64-gnu': 1.89.0 + '@oxlint/binding-linux-riscv64-musl': 1.89.0 + '@oxlint/binding-linux-s390x-gnu': 1.89.0 + '@oxlint/binding-linux-x64-gnu': 1.89.0 + '@oxlint/binding-linux-x64-musl': 1.89.0 + '@oxlint/binding-openharmony-arm64': 1.89.0 + '@oxlint/binding-win32-arm64-msvc': 1.89.0 + '@oxlint/binding-win32-ia32-msvc': 1.89.0 + '@oxlint/binding-win32-x64-msvc': 1.89.0 oxlint-tsgolint: 7.0.2004 - vite-plus: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + vite-plus: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) - oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): + oxlint@1.89.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.88.0 - '@oxlint/binding-android-arm64': 1.88.0 - '@oxlint/binding-darwin-arm64': 1.88.0 - '@oxlint/binding-darwin-x64': 1.88.0 - '@oxlint/binding-freebsd-x64': 1.88.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.88.0 - '@oxlint/binding-linux-arm-musleabihf': 1.88.0 - '@oxlint/binding-linux-arm64-gnu': 1.88.0 - '@oxlint/binding-linux-arm64-musl': 1.88.0 - '@oxlint/binding-linux-ppc64-gnu': 1.88.0 - '@oxlint/binding-linux-riscv64-gnu': 1.88.0 - '@oxlint/binding-linux-riscv64-musl': 1.88.0 - '@oxlint/binding-linux-s390x-gnu': 1.88.0 - '@oxlint/binding-linux-x64-gnu': 1.88.0 - '@oxlint/binding-linux-x64-musl': 1.88.0 - '@oxlint/binding-openharmony-arm64': 1.88.0 - '@oxlint/binding-win32-arm64-msvc': 1.88.0 - '@oxlint/binding-win32-ia32-msvc': 1.88.0 - '@oxlint/binding-win32-x64-msvc': 1.88.0 + '@oxlint/binding-android-arm-eabi': 1.89.0 + '@oxlint/binding-android-arm64': 1.89.0 + '@oxlint/binding-darwin-arm64': 1.89.0 + '@oxlint/binding-darwin-x64': 1.89.0 + '@oxlint/binding-freebsd-x64': 1.89.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.89.0 + '@oxlint/binding-linux-arm-musleabihf': 1.89.0 + '@oxlint/binding-linux-arm64-gnu': 1.89.0 + '@oxlint/binding-linux-arm64-musl': 1.89.0 + '@oxlint/binding-linux-ppc64-gnu': 1.89.0 + '@oxlint/binding-linux-riscv64-gnu': 1.89.0 + '@oxlint/binding-linux-riscv64-musl': 1.89.0 + '@oxlint/binding-linux-s390x-gnu': 1.89.0 + '@oxlint/binding-linux-x64-gnu': 1.89.0 + '@oxlint/binding-linux-x64-musl': 1.89.0 + '@oxlint/binding-openharmony-arm64': 1.89.0 + '@oxlint/binding-win32-arm64-msvc': 1.89.0 + '@oxlint/binding-win32-ia32-msvc': 1.89.0 + '@oxlint/binding-win32-x64-msvc': 1.89.0 oxlint-tsgolint: 7.0.2004 - vite-plus: 1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) + vite-plus: 1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) parse5@8.0.1: dependencies: @@ -6199,31 +6318,32 @@ snapshots: picocolors: 1.1.1 picomatch: 2.3.2 - vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2): + vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2): dependencies: - '@oxc-project/types': 0.154.0 - '@oxlint/plugins': 1.88.0 - '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) - '@vitest/browser-preview': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + '@oxc-project/runtime': 0.155.0 + '@oxc-project/types': 0.155.0 + '@oxlint/plugins': 1.89.0 + '@vitest/browser': 5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) + '@vitest/browser-preview': 5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) + '@vitest/mocker': 5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) '@vitest/pretty-format': 5.0.5 '@vitest/snapshot': 5.0.5 '@vitest/spy': 5.0.5 '@vitest/utils': 5.0.5 - oxfmt: 0.73.0(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) - oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + oxfmt: 0.74.0(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + oxlint: 1.89.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) oxlint-tsgolint: 7.0.2004 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' - vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3) + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) optionalDependencies: - '@voidzero-dev/vite-plus-darwin-arm64': 1.3.0 - '@voidzero-dev/vite-plus-darwin-x64': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-musl': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-musl': 1.3.0 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.3.0 - '@voidzero-dev/vite-plus-win32-x64-msvc': 1.3.0 + '@voidzero-dev/vite-plus-darwin-arm64': 1.4.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.4.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.4.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.4.0 transitivePeerDependencies: - '@arethetypeswrong/core' - '@astrojs/compiler-rs' @@ -6259,31 +6379,32 @@ snapshots: - vue-tsc - yaml - vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2): + vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2): dependencies: - '@oxc-project/types': 0.154.0 - '@oxlint/plugins': 1.88.0 - '@vitest/browser': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) - '@vitest/browser-preview': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + '@oxc-project/runtime': 0.155.0 + '@oxc-project/types': 0.155.0 + '@oxlint/plugins': 1.89.0 + '@vitest/browser': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) + '@vitest/browser-preview': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) + '@vitest/mocker': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) '@vitest/pretty-format': 5.0.5 '@vitest/snapshot': 5.0.5 '@vitest/spy': 5.0.5 '@vitest/utils': 5.0.5 - oxfmt: 0.73.0(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) - oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.3.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + oxfmt: 0.74.0(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + oxlint: 1.89.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.4.0(@types/node@22.20.5)(@vitest/ui@5.0.5)(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) oxlint-tsgolint: 7.0.2004 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' - vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3) + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vitest: 5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) optionalDependencies: - '@voidzero-dev/vite-plus-darwin-arm64': 1.3.0 - '@voidzero-dev/vite-plus-darwin-x64': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-arm64-musl': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-gnu': 1.3.0 - '@voidzero-dev/vite-plus-linux-x64-musl': 1.3.0 - '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.3.0 - '@voidzero-dev/vite-plus-win32-x64-msvc': 1.3.0 + '@voidzero-dev/vite-plus-darwin-arm64': 1.4.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.4.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.4.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.4.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.4.0 transitivePeerDependencies: - '@arethetypeswrong/core' - '@astrojs/compiler-rs' @@ -6319,10 +6440,10 @@ snapshots: - vue-tsc - yaml - vitest@5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3): + vitest@5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@1.21.7)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + '@vitest/mocker': 5.0.5(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 @@ -6333,21 +6454,41 @@ snapshots: tinybench: 6.2.2 tinyexec: 1.3.1 tinyglobby: 0.2.17 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@1.21.7)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 22.20.5 - '@vitest/browser-preview': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) + '@vitest/browser-preview': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) '@vitest/ui': 5.0.5(vitest@5.0.5) happy-dom: 21.0.4 jsdom: 30.1.3 transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - vue-tsc + - yaml - vitest@5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jsdom@30.1.3): + vitest@5.0.5(@types/node@22.20.5)(@vitest/browser-preview@5.0.5)(@vitest/ui@5.0.5)(@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(happy-dom@21.0.4)(jiti@2.7.0)(jsdom@30.1.3)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2): dependencies: '@types/chai': 5.2.3 - '@vitest/mocker': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)) + '@vitest/mocker': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2) chai: 6.3.0 es-module-lexer: 2.3.2 expect-type: 1.4.0 @@ -6358,16 +6499,36 @@ snapshots: tinybench: 6.2.2 tinyexec: 1.3.1 tinyglobby: 0.2.17 - vite: '@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' + vite: '@voidzero-dev/vite-plus-core@1.4.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)' why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 22.20.5 - '@vitest/browser-preview': 5.0.5(@voidzero-dev/vite-plus-core@1.3.0(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2))(vitest@5.0.5) + '@vitest/browser-preview': 5.0.5(@types/node@22.20.5)(jiti@2.7.0)(sass@1.106.0)(terser@5.51.2)(typescript@7.0.2)(vitest@5.0.5) '@vitest/ui': 5.0.5(vitest@5.0.5) happy-dom: 21.0.4 jsdom: 30.1.3 transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@volar/typescript' + - '@vue/language-core' + - esbuild + - jiti + - less - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - vue-tsc + - yaml vue-component-type-helpers@3.3.13: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 9ad013edd8..ad8e8b9649 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -14,7 +14,7 @@ allowBuilds: # report a peer version mismatch despite using the bundled Vite runtime. # Update both core aliases with the vite-plus catalog pin; match Vitest to `vp toolchain vitest`. overrides: - 'vite@*': npm:@voidzero-dev/vite-plus-core@1.3.0 + 'vite@*': npm:@voidzero-dev/vite-plus-core@1.4.0 'vitest@*': 5.0.5 # Supply-chain defense: require packages to be at least 7 days old before @@ -39,8 +39,8 @@ catalog: sass: ^1.106.0 tailwindcss: ^4.3.3 typescript: ^7.0.2 - vite: npm:@voidzero-dev/vite-plus-core@1.3.0 - vite-plus: 1.3.0 + vite: npm:@voidzero-dev/vite-plus-core@1.4.0 + vite-plus: 1.4.0 vue: ^3.5.44 vue-json-pretty: ^2.6.0 vue-router: ^4.6.4 diff --git a/src/foundation/resources/exceptions/renderer/dist/scripts.js b/src/foundation/resources/exceptions/renderer/dist/scripts.js index 961d7803f8..b8ca1d2f81 100644 --- a/src/foundation/resources/exceptions/renderer/dist/scripts.js +++ b/src/foundation/resources/exceptions/renderer/dist/scripts.js @@ -2,7 +2,7 @@ var e=Object.defineProperty,t=(t,n)=>{let r={};for(var i in t)e(r,i,{get:t[i],en ${n?`Expression: "`+n+`" -`:``}`,t),setTimeout(()=>{throw e},0)}var Re=!0;function ze(e){let t=Re;Re=!1;let n=e();return Re=t,n}function Be(e,t,n={}){let r;return F(e,t)(e=>r=e,n),r}function F(...e){return Ve(...e)}var Ve=()=>{};function He(e){Ve=e}var Ue;function We(e){Ue=e}function Ge(e,t){let n={};je(n,e);let r=[n,...be(e)],i=typeof t==`function`?Ke(r,t):Ye(r,t,e);return Ne.bind(null,e,t,i)}function Ke(e,t){return(n=()=>{},{scope:r={},params:i=[],context:a}={})=>{Re?Xe(n,t.apply(xe([r,...e]),i)):Xe(n,t,xe([r,...e]),i)}}var qe={};function Je(e,t){if(qe[e])return qe[e];let n=Object.getPrototypeOf(async function(){}).constructor,r=/^[\n\s]*if.*\(.*\)/.test(e.trim())||/^(let|const)\s/.test(e.trim())?`(async()=>{ ${e} })()`:e,i=(()=>{try{let t=new n([`__self`,`scope`],`with (scope) { __self.result = ${r} }; __self.finished = true; return __self.result;`);return Object.defineProperty(t,"name",{value:`[Alpine] ${e}`}),t}catch(n){return Pe(n,t,e),Promise.resolve()}})();return qe[e]=i,i}function Ye(e,t,n){let r=Je(t,n);return(i=()=>{},{scope:a={},params:o=[],context:s}={})=>{r.result=void 0,r.finished=!1;let c=xe([a,...e]);if(typeof r==`function`){let e=r.call(s,r,c).catch(e=>Pe(e,n,t));r.finished?(Xe(i,r.result,c,o,n),r.result=void 0):e.then(e=>{Xe(i,e,c,o,n)}).catch(e=>Pe(e,n,t)).finally(()=>r.result=void 0)}}}function Xe(e,t,n,r,i){if(Re&&typeof t==`function`){let a=t.apply(n,r);a instanceof Promise?a.then(t=>Xe(e,t,n,r)).catch(e=>Pe(e,i,t)):e(a)}else typeof t==`object`&&t instanceof Promise?t.then(t=>e(t)):e(t)}function Ze(...e){return Ue(...e)}function Qe(e,t,n={}){let r={};je(r,e);let i=[r,...be(e)],a=xe([n.scope??{},...i]),o=n.params??[];if(t.includes(`await`)){let e=Object.getPrototypeOf(async function(){}).constructor;return new e([`scope`],`with (scope) { let __result = ${/^[\n\s]*if.*\(.*\)/.test(t.trim())||/^(let|const)\s/.test(t.trim())?`(async()=>{ ${t} })()`:t}; return __result }`).call(n.context,a)}{let e=/^[\n\s]*if.*\(.*\)/.test(t.trim())||/^(let|const)\s/.test(t.trim())?`(()=>{ ${t} })()`:t,r=Function([`scope`],`with (scope) { let __result = ${e}; return __result }`).call(n.context,a);return typeof r==`function`&&Re?r.apply(a,o):r}}var $e=`x-`;function et(e=``){return $e+e}function tt(e){$e=e}var nt={};function I(e,t){return nt[e]=t,{before(t){if(!nt[t]){console.warn(String.raw`Cannot find directive \`${t}\`. \`${e}\` will use the default order of execution`);return}let n=xt.indexOf(t);xt.splice(n>=0?n:xt.indexOf(`DEFAULT`),0,e)}}}function rt(e){return Object.keys(nt).includes(e)}function it(e,t,n){if(t=Array.from(t),e._x_virtualDirectives){let n=Object.entries(e._x_virtualDirectives).map(([e,t])=>({name:e,value:t})),r=at(n);n=n.map(e=>r.find(t=>t.name===e.name)?{name:`x-bind:${e.name}`,value:`"${e.value}"`}:e),t=t.concat(n)}let r={};return t.map(mt((e,t)=>r[e]=t)).filter(_t).map(yt(r,n)).sort(St).map(t=>dt(e,t))}function at(e){return Array.from(e).map(mt()).filter(e=>!_t(e))}var ot=!1,st=new Map,ct=Symbol();function lt(e){ot=!0;let t=Symbol();ct=t,st.set(t,[]);let n=()=>{for(;st.get(t).length;)st.get(t).shift()();st.delete(t)};e(n),ot=!1,n()}function ut(e){let t=[],n=e=>t.push(e),[r,i]=O(e);return t.push(i),[{Alpine:ur,effect:r,cleanup:n,evaluateLater:F.bind(F,e),evaluate:Be.bind(Be,e)},()=>t.forEach(e=>e())]}function dt(e,t){let n=nt[t.type]||(()=>{}),[r,i]=ut(e);ie(e,t.original,i);let a=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,r),n=n.bind(n,e,t,r),ot?st.get(ct).push(n):n())};return a.runCleanups=i,a}var ft=(e,t)=>({name:n,value:r})=>(n.startsWith(e)&&(n=n.replace(e,t)),{name:n,value:r}),pt=e=>e;function mt(e=()=>{}){return({name:t,value:n})=>{let{name:r,value:i}=ht.reduce((e,t)=>t(e),{name:t,value:n});return r!==t&&e(r,t),{name:r,value:i}}}var ht=[];function gt(e){ht.push(e)}function _t({name:e}){return vt().test(e)}var vt=()=>RegExp(`^${$e}([^:^.]+)\\b`);function yt(e,t){return({name:n,value:r})=>{n===r&&(r=``);let i=n.match(vt()),a=n.match(/:([a-zA-Z0-9\-_:]+)/),o=n.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],s=t||e[n]||n;return{type:i?i[1]:null,value:a?a[1]:null,modifiers:o.map(e=>e.replace(`.`,``)),expression:r,original:s}}}var bt=`DEFAULT`,xt=[`ignore`,`ref`,`id`,`data`,`anchor`,`bind`,`init`,`for`,`model`,`modelable`,`transition`,`show`,`if`,bt,`teleport`];function St(e,t){let n=xt.indexOf(e.type)===-1?bt:e.type,r=xt.indexOf(t.type)===-1?bt:t.type;return xt.indexOf(n)-xt.indexOf(r)}function Ct(e,t){if(typeof ShadowRoot==`function`&&e instanceof ShadowRoot){Array.from(e.children).forEach(e=>Ct(e,t));return}let n=!1;if(t(e,()=>n=!0),n)return;let r=e.firstElementChild;for(;r;)Ct(r,t,!1),r=r.nextElementSibling}var wt=!1;function Tt(e,t=()=>{}){return(...n)=>wt?t(...n):e(...n)}function Et(e){return(...t)=>wt&&e(...t)}var Dt=[];function Ot(e){Dt.push(e)}function kt(e,t){Dt.forEach(n=>n(e,t)),wt=!0,Nt(()=>{nn(t,(e,t)=>{t(e,()=>{})})}),wt=!1}var At=!1;function jt(e,t){t._x_dataStack||=e._x_dataStack,wt=!0,At=!0,Nt(()=>{Mt(t)}),wt=!1,At=!1}function Mt(e){let t=!1;nn(e,(e,n)=>{Ct(e,(e,r)=>{if(t&&Qt(e))return r();t=!0,n(e,r)})})}function Nt(e){let t=b;D((e,n)=>{let r=t(e);return x(r),()=>{}}),e(),D(t)}var Pt=0;function Ft(e,t){let n=e._x_deferInit;n||(n=e._x_deferInit={pending:0,ownsIgnore:!e._x_ignore,queuedAttributes:new Map},n.ownsIgnore&&(e._x_ignore=!0),Pt++),n.pending++,Promise.resolve(t).catch(t=>{try{Pe(t,e)}catch(e){setTimeout(()=>{throw e},0)}}).then(()=>It(e,n))}function It(e,t){t.pending--,!(t.pending>0)&&(pe(),!(t.pending>0)&&e._x_deferInit===t&&(delete e._x_deferInit,t.ownsIgnore&&delete e._x_ignore,Pt--,e.isConnected&&(zt(t),nn(e))))}function Lt(e,t){if(Pt===0)return!1;let n=Zt(e,e=>e._x_deferInit);return n?(Rt(n._x_deferInit,e,t.map(({name:e})=>e)),!0):!1}function Rt(e,t,n){let r=e.queuedAttributes.get(t);(!r||r.marker!==t._x_marker)&&(r={marker:t._x_marker,names:new Set},e.queuedAttributes.set(t,r)),n.forEach(e=>r.names.add(e))}function zt(e){e.queuedAttributes.forEach((e,t)=>{if(!t.isConnected||!t._x_marker||t._x_marker!==e.marker)return;let n=Zt(t,e=>e._x_deferInit);if(n){Rt(n._x_deferInit,t,Array.from(e.names));return}let r=Array.from(e.names).filter(e=>t.hasAttribute(e)).map(e=>({name:e,value:t.getAttribute(e)}));r.length!==0&&it(t,r).forEach(e=>e())})}Ot((e,t)=>{Pt!==0&&e&&e.nodeType===1&&t&&t.nodeType===1&&Zt(e,e=>e._x_deferInit)&&(t._x_ignore=!0)});function Bt(e,t,n={},r={}){return e.dispatchEvent(new CustomEvent(t,{detail:n,bubbles:!0,composed:!0,cancelable:!0,...r}))}function Vt(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var Ht=!1;function Ut(){Ht&&Vt(`Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems.`),Ht=!0,document.body||Vt("Unable to initialize. Trying to load Alpine before `
` is available. Did you forget to add `defer` in Alpine's `'])); + + $this->assertStringNotContainsString('', $html); + } +} From a5d954f73bc9db03902020ff8b61cad0006bcf10 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Sun, 1 Nov 2026 17:26:06 +0000 Subject: [PATCH 21/36] Prevent framing of MCP authorization and device pages Port laravel/mcp#376 at 79c6fedc6d5dc21cef1040f2e4fbedc8ae980b8d. Register stateless framing middleware through the existing global middleware boundary. Cover all seven Passport authorization/device routes while preserving existing CSP directives and leaving unrelated responses unchanged. Validation: middleware unit tests pass (10/18), real-kernel feature tests pass (2/8), and provider tests pass (2/3). Full source/type analysis and formatting pass. --- src/mcp/src/Server/McpServiceProvider.php | 4 +- .../AddAuthorizationFramingHeaders.php | 31 +++++++++ .../AddAuthorizationFramingHeadersTest.php | 31 +++++++++ .../Unit/Server/McpServiceProviderTest.php | 7 ++ .../AddAuthorizationFramingHeadersTest.php | 68 +++++++++++++++++++ 5 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/mcp/src/Server/Middleware/AddAuthorizationFramingHeaders.php create mode 100644 tests/Mcp/Feature/Server/Middleware/AddAuthorizationFramingHeadersTest.php create mode 100644 tests/Mcp/Unit/Server/Middleware/AddAuthorizationFramingHeadersTest.php diff --git a/src/mcp/src/Server/McpServiceProvider.php b/src/mcp/src/Server/McpServiceProvider.php index 0a49286cb5..ce1cf8b0c2 100644 --- a/src/mcp/src/Server/McpServiceProvider.php +++ b/src/mcp/src/Server/McpServiceProvider.php @@ -16,6 +16,7 @@ use Hypervel\Mcp\Console\Commands\MakeToolCommand; use Hypervel\Mcp\Console\Commands\StartCommand; use Hypervel\Mcp\Request; +use Hypervel\Mcp\Server\Middleware\AddAuthorizationFramingHeaders; use Hypervel\Mcp\Server\Middleware\AddWwwAuthenticateHeader; use Hypervel\Support\Facades\Route; use Hypervel\Support\ServiceProvider; @@ -53,13 +54,14 @@ public function boot(): void } /** - * Register the authentication challenge middleware. + * Register the authentication challenge and authorization framing middleware. */ protected function registerGlobalMiddleware(): void { $this->callAfterResolving(HttpKernelContract::class, function (mixed $kernel): void { if ($kernel instanceof HttpKernel) { $kernel->pushMiddleware(AddWwwAuthenticateHeader::class); + $kernel->pushMiddleware(AddAuthorizationFramingHeaders::class); } }); } diff --git a/src/mcp/src/Server/Middleware/AddAuthorizationFramingHeaders.php b/src/mcp/src/Server/Middleware/AddAuthorizationFramingHeaders.php new file mode 100644 index 0000000000..9c1d362ad3 --- /dev/null +++ b/src/mcp/src/Server/Middleware/AddAuthorizationFramingHeaders.php @@ -0,0 +1,31 @@ +routeIs('passport.authorizations.*', 'passport.device', 'passport.device.authorizations.*')) { + return $response; + } + + $response->headers->set('X-Frame-Options', 'DENY'); + $response->headers->set('Content-Security-Policy', "frame-ancestors 'none'", false); + + return $response; + } +} diff --git a/tests/Mcp/Feature/Server/Middleware/AddAuthorizationFramingHeadersTest.php b/tests/Mcp/Feature/Server/Middleware/AddAuthorizationFramingHeadersTest.php new file mode 100644 index 0000000000..6830bfa6b2 --- /dev/null +++ b/tests/Mcp/Feature/Server/Middleware/AddAuthorizationFramingHeadersTest.php @@ -0,0 +1,31 @@ + 'consent')->name('passport.authorizations.authorize'); + + $this->get('oauth/authorize') + ->assertOk() + ->assertHeader('X-Frame-Options', 'DENY') + ->assertHeader('Content-Security-Policy', "frame-ancestors 'none'"); + } + + public function testDoesNotAddFramingHeadersToOtherRoutes(): void + { + Route::get('dashboard', fn (): string => 'ok')->name('dashboard'); + + $this->get('dashboard') + ->assertOk() + ->assertHeaderMissing('X-Frame-Options') + ->assertHeaderMissing('Content-Security-Policy'); + } +} diff --git a/tests/Mcp/Unit/Server/McpServiceProviderTest.php b/tests/Mcp/Unit/Server/McpServiceProviderTest.php index 7963b2b668..6c6cf215ed 100644 --- a/tests/Mcp/Unit/Server/McpServiceProviderTest.php +++ b/tests/Mcp/Unit/Server/McpServiceProviderTest.php @@ -4,7 +4,9 @@ namespace Hypervel\Tests\Mcp\Unit\Server; +use Hypervel\Contracts\Http\Kernel; use Hypervel\Mcp\Server\McpServiceProvider; +use Hypervel\Mcp\Server\Middleware\AddAuthorizationFramingHeaders; use Hypervel\Passport\Passport; use Hypervel\Tests\Mcp\TestCase; @@ -23,4 +25,9 @@ public function testRegistersMcpScopeDuringBoot(): void $this->assertArrayHasKey('mcp:use', Passport::$scopes); $this->assertSame('Custom scope', Passport::$scopes['custom']); } + + public function testRegistersTheAuthorizationFramingMiddlewareGlobally(): void + { + $this->assertTrue(app(Kernel::class)->hasMiddleware(AddAuthorizationFramingHeaders::class)); + } } diff --git a/tests/Mcp/Unit/Server/Middleware/AddAuthorizationFramingHeadersTest.php b/tests/Mcp/Unit/Server/Middleware/AddAuthorizationFramingHeadersTest.php new file mode 100644 index 0000000000..235b15eea7 --- /dev/null +++ b/tests/Mcp/Unit/Server/Middleware/AddAuthorizationFramingHeadersTest.php @@ -0,0 +1,68 @@ +setRouteResolver(fn (): Route => (new Route('GET', '/oauth/authorize', []))->name($name)); + + $response = (new AddAuthorizationFramingHeaders)->handle($request, fn (): Response => response('consent')); + + $this->assertSame('DENY', $response->headers->get('X-Frame-Options')); + $this->assertSame("frame-ancestors 'none'", $response->headers->get('Content-Security-Policy')); + } + + public function testReplacesAWeakerFrameOptionsHeader(): void + { + $request = Request::create('/oauth/authorize'); + $request->setRouteResolver(fn (): Route => (new Route('GET', '/oauth/authorize', []))->name('passport.authorizations.authorize')); + + $response = (new AddAuthorizationFramingHeaders)->handle($request, fn (): Response => response('consent', 200, [ + 'X-Frame-Options' => 'SAMEORIGIN', + ])); + + $this->assertSame(['DENY'], $response->headers->all('x-frame-options')); + } + + public function testKeepsTheContentSecurityPolicyTheApplicationAlreadySet(): void + { + $request = Request::create('/oauth/authorize'); + $request->setRouteResolver(fn (): Route => (new Route('GET', '/oauth/authorize', []))->name('passport.authorizations.authorize')); + + $response = (new AddAuthorizationFramingHeaders)->handle($request, fn (): Response => response('consent', 200, [ + 'Content-Security-Policy' => "default-src 'self'", + ])); + + $this->assertSame(["default-src 'self'", "frame-ancestors 'none'"], $response->headers->all('content-security-policy')); + } + + public function testLeavesOtherResponsesUntouched(): void + { + $request = Request::create('/mcp'); + $request->setRouteResolver(fn (): Route => (new Route('POST', '/mcp', []))->name('mcp')); + + $response = (new AddAuthorizationFramingHeaders)->handle($request, fn (): Response => response('ok')); + + $this->assertFalse($response->headers->has('X-Frame-Options')); + $this->assertFalse($response->headers->has('Content-Security-Policy')); + } +} From 2acc04e1054a73c08c1d80d27120f9e7d9f8ebfd Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Sun, 1 Nov 2026 17:26:06 +0000 Subject: [PATCH 22/36] Preserve batch embedding input order across indexed providers Port laravel/ai#1172 at 1b071b5d9b056a84c33900abe871d51299a0b218. Sort indexed OpenAI and Azure responses before extracting vectors, and apply the same correction to the compatible and OpenRouter gateways. Preserve vector validation and the existing generation/cache flow. Extend existing parsing cases rather than duplicating ordered-response coverage. Validation: shuffled-response regressions fail before the fix. Complete provider files pass: OpenAI 12/17, Azure 11/14, compatible 16/30, OpenRouter 9/16. Full source/type analysis and the AI dogfood suite pass. --- .../AzureOpenAi/AzureOpenAiGateway.php | 2 +- src/ai/src/Gateway/OpenAi/OpenAiGateway.php | 2 +- .../OpenAiCompatibleGateway.php | 2 +- .../Gateway/OpenRouter/OpenRouterGateway.php | 2 +- .../Providers/AzureOpenAi/EmbeddingTest.php | 23 +++++++++++++++++++ .../Providers/OpenAi/EmbeddingTest.php | 21 +++++++++++++++++ .../OpenAiCompatible/EmbeddingsTest.php | 15 ++++++++---- .../Providers/OpenRouter/EmbeddingsTest.php | 15 ++++++++---- 8 files changed, 68 insertions(+), 14 deletions(-) diff --git a/src/ai/src/Gateway/AzureOpenAi/AzureOpenAiGateway.php b/src/ai/src/Gateway/AzureOpenAi/AzureOpenAiGateway.php index 5045ff5d50..08de4c9c04 100644 --- a/src/ai/src/Gateway/AzureOpenAi/AzureOpenAiGateway.php +++ b/src/ai/src/Gateway/AzureOpenAi/AzureOpenAiGateway.php @@ -80,7 +80,7 @@ public function generateEmbeddings( $data = $response->json(); return new EmbeddingsResponse( - collect($data['data'] ?? [])->pluck('embedding')->all(), + collect($data['data'] ?? [])->sortBy('index')->pluck('embedding')->all(), new Usage($data['usage']['prompt_tokens'] ?? 0), new Meta($provider->name(), $model), ); diff --git a/src/ai/src/Gateway/OpenAi/OpenAiGateway.php b/src/ai/src/Gateway/OpenAi/OpenAiGateway.php index 0dc1005763..11bafa6a43 100644 --- a/src/ai/src/Gateway/OpenAi/OpenAiGateway.php +++ b/src/ai/src/Gateway/OpenAi/OpenAiGateway.php @@ -287,7 +287,7 @@ public function generateEmbeddings( $data = $response->json(); return new EmbeddingsResponse( - collect($data['data'] ?? [])->pluck('embedding')->all(), + collect($data['data'] ?? [])->sortBy('index')->pluck('embedding')->all(), new Usage($data['usage']['prompt_tokens'] ?? 0), new Meta($provider->name(), $model), ); diff --git a/src/ai/src/Gateway/OpenAiCompatible/OpenAiCompatibleGateway.php b/src/ai/src/Gateway/OpenAiCompatible/OpenAiCompatibleGateway.php index 1b0a1df06e..dfccc65cf5 100644 --- a/src/ai/src/Gateway/OpenAiCompatible/OpenAiCompatibleGateway.php +++ b/src/ai/src/Gateway/OpenAiCompatible/OpenAiCompatibleGateway.php @@ -100,7 +100,7 @@ public function generateEmbeddings( */ protected function parseEmbeddings(array $data, int $expectedCount): array { - $embeddings = (new Collection($data['data'] ?? []))->pluck('embedding'); + $embeddings = (new Collection($data['data'] ?? []))->sortBy('index')->pluck('embedding'); if ($embeddings->count() !== $expectedCount) { throw new AiException('OpenAI-compatible Error: [invalid_response] The response did not contain the expected number of embeddings.'); diff --git a/src/ai/src/Gateway/OpenRouter/OpenRouterGateway.php b/src/ai/src/Gateway/OpenRouter/OpenRouterGateway.php index b9089d1e32..13b34c3490 100644 --- a/src/ai/src/Gateway/OpenRouter/OpenRouterGateway.php +++ b/src/ai/src/Gateway/OpenRouter/OpenRouterGateway.php @@ -349,7 +349,7 @@ public function generateEmbeddings( $this->validateTextResponse($data); return new EmbeddingsResponse( - (new Collection($data['data'] ?? []))->pluck('embedding')->all(), + (new Collection($data['data'] ?? []))->sortBy('index')->pluck('embedding')->all(), new Usage($data['usage']['prompt_tokens'] ?? 0), new Meta($provider->name(), $model), ); diff --git a/tests/Ai/Feature/Providers/AzureOpenAi/EmbeddingTest.php b/tests/Ai/Feature/Providers/AzureOpenAi/EmbeddingTest.php index 1d9cdf67f4..7b68ab0506 100644 --- a/tests/Ai/Feature/Providers/AzureOpenAi/EmbeddingTest.php +++ b/tests/Ai/Feature/Providers/AzureOpenAi/EmbeddingTest.php @@ -103,6 +103,29 @@ public function testMultipleInputsReturnMultipleEmbeddings(): void $this->assertCount(2, $response->embeddings); } + public function testEmbeddingsAreReturnedInInputOrderEvenWhenTheResponseIsShuffled(): void + { + Http::fake(['*' => Http::response([ + 'id' => 'embd-123', + 'object' => 'list', + 'model' => 'text-embedding-3-small', + 'data' => [ + ['object' => 'embedding', 'index' => 2, 'embedding' => [0.7, 0.8, 0.9]], + ['object' => 'embedding', 'index' => 0, 'embedding' => [0.1, 0.2, 0.3]], + ['object' => 'embedding', 'index' => 1, 'embedding' => [0.4, 0.5, 0.6]], + ], + 'usage' => ['prompt_tokens' => 30], + ])]); + + $response = Embeddings::for(['Hello', 'World', 'Again'])->generate(provider: 'azure', model: 'text-embedding-3-small'); + + $this->assertSame([ + [0.1, 0.2, 0.3], + [0.4, 0.5, 0.6], + [0.7, 0.8, 0.9], + ], $response->embeddings); + } + public function testEmbeddingsRateLimitResponseThrowsRateLimitedException(): void { $this->expectException(RateLimitedException::class); diff --git a/tests/Ai/Feature/Providers/OpenAi/EmbeddingTest.php b/tests/Ai/Feature/Providers/OpenAi/EmbeddingTest.php index 1261d70d6a..c1c6d2085e 100644 --- a/tests/Ai/Feature/Providers/OpenAi/EmbeddingTest.php +++ b/tests/Ai/Feature/Providers/OpenAi/EmbeddingTest.php @@ -70,6 +70,27 @@ public function testMultipleInputsReturnMultipleEmbeddings(): void $this->assertSame([0.4, 0.5, 0.6], $response->embeddings[1]); } + public function testEmbeddingsAreReturnedInInputOrderEvenWhenTheResponseIsShuffled(): void + { + Http::fake(['*' => Http::response([ + 'object' => 'list', + 'data' => [ + ['object' => 'embedding', 'index' => 2, 'embedding' => [0.7, 0.8, 0.9]], + ['object' => 'embedding', 'index' => 0, 'embedding' => [0.1, 0.2, 0.3]], + ['object' => 'embedding', 'index' => 1, 'embedding' => [0.4, 0.5, 0.6]], + ], + 'usage' => ['prompt_tokens' => 30], + ])]); + + $response = Embeddings::for(['Hello', 'World', 'Again'])->generate(provider: 'openai', model: 'text-embedding-3-small'); + + $this->assertSame([ + [0.1, 0.2, 0.3], + [0.4, 0.5, 0.6], + [0.7, 0.8, 0.9], + ], $response->embeddings); + } + public function testEmbeddingsRequestSendsBearerToken(): void { Http::fake(['*' => $this->fakeOpenAiEmbeddingResponse()]); diff --git a/tests/Ai/Feature/Providers/OpenAiCompatible/EmbeddingsTest.php b/tests/Ai/Feature/Providers/OpenAiCompatible/EmbeddingsTest.php index 3dfa68f470..beb2907992 100644 --- a/tests/Ai/Feature/Providers/OpenAiCompatible/EmbeddingsTest.php +++ b/tests/Ai/Feature/Providers/OpenAiCompatible/EmbeddingsTest.php @@ -14,6 +14,7 @@ use Hypervel\Support\Facades\Http; use Hypervel\Tests\Ai\TestCase; use InvalidArgumentException; +use PHPUnit\Framework\Attributes\TestWith; use RuntimeException; class EmbeddingsTest extends TestCase @@ -56,14 +57,18 @@ public function testEmbeddingsRequestIsCorrectlyFormatted(): void }); } - public function testEmbeddingsResponseIsCorrectlyParsed(): void + #[TestWith([false])] + #[TestWith([true])] + public function testEmbeddingsResponseIsCorrectlyParsed(bool $shuffled): void { + $embeddings = [ + ['object' => 'embedding', 'index' => 0, 'embedding' => [0.1, 0.2, 0.3]], + ['object' => 'embedding', 'index' => 1, 'embedding' => [0.4, 0.5, 0.6]], + ]; + Http::fake(['*' => Http::response([ 'object' => 'list', - 'data' => [ - ['object' => 'embedding', 'index' => 0, 'embedding' => [0.1, 0.2, 0.3]], - ['object' => 'embedding', 'index' => 1, 'embedding' => [0.4, 0.5, 0.6]], - ], + 'data' => $shuffled ? array_reverse($embeddings) : $embeddings, 'usage' => ['prompt_tokens' => 10], ])]); diff --git a/tests/Ai/Feature/Providers/OpenRouter/EmbeddingsTest.php b/tests/Ai/Feature/Providers/OpenRouter/EmbeddingsTest.php index a211359765..c67effda97 100644 --- a/tests/Ai/Feature/Providers/OpenRouter/EmbeddingsTest.php +++ b/tests/Ai/Feature/Providers/OpenRouter/EmbeddingsTest.php @@ -12,6 +12,7 @@ use Hypervel\Http\Client\Request; use Hypervel\Support\Facades\Http; use Hypervel\Tests\Ai\TestCase; +use PHPUnit\Framework\Attributes\TestWith; class EmbeddingsTest extends TestCase { @@ -45,14 +46,18 @@ public function testEmbeddingsRequestIsCorrectlyFormatted(): void }); } - public function testEmbeddingsResponseIsCorrectlyParsed(): void + #[TestWith([false])] + #[TestWith([true])] + public function testEmbeddingsResponseIsCorrectlyParsed(bool $shuffled): void { + $embeddings = [ + ['object' => 'embedding', 'index' => 0, 'embedding' => [0.1, 0.2, 0.3]], + ['object' => 'embedding', 'index' => 1, 'embedding' => [0.4, 0.5, 0.6]], + ]; + Http::fake(['*' => Http::response([ 'object' => 'list', - 'data' => [ - ['object' => 'embedding', 'index' => 0, 'embedding' => [0.1, 0.2, 0.3]], - ['object' => 'embedding', 'index' => 1, 'embedding' => [0.4, 0.5, 0.6]], - ], + 'data' => $shuffled ? array_reverse($embeddings) : $embeddings, 'usage' => ['prompt_tokens' => 10], ])]); From c53d45d59844afaf2c78692126d624a42e31fc76 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Sun, 1 Nov 2026 17:29:42 +0000 Subject: [PATCH 23/36] Preserve all Vary values on Inertia and precognitive responses Port inertiajs/inertia-laravel #920 (d25c67c9ec849b743ebbd3d656a0baf8656a93bf) and #919 (4e91cf45cad5ef97862d1ede40fe41616d4e4f91) from the recorded 3.x target 1e8706260b975c1c652dd886a53657e0fae2102d. Normalize Vary into one header line while retaining case-insensitive Inertia detection and final-response placement. Preserve zero, streamed and file responses with the complete upstream assertions. Use native arrays instead of allocating a Collection for each response. Fix the related precognition path dropping later Vary lines by joining every existing value. The multi-line regression fails before the correction. Complete MiddlewareTest (58 tests, 174 assertions) and PrecognitionTest (57 tests, 266 assertions), formatting and source analysis pass. --- .../Middleware/HandlePrecognitiveRequests.php | 3 +- src/inertia/src/Middleware.php | 10 +- tests/Inertia/MiddlewareTest.php | 117 +++++++++++++----- .../Integration/Routing/PrecognitionTest.php | 13 +- 4 files changed, 97 insertions(+), 46 deletions(-) diff --git a/src/foundation/src/Http/Middleware/HandlePrecognitiveRequests.php b/src/foundation/src/Http/Middleware/HandlePrecognitiveRequests.php index cd79f33a57..d30cb58ffb 100644 --- a/src/foundation/src/Http/Middleware/HandlePrecognitiveRequests.php +++ b/src/foundation/src/Http/Middleware/HandlePrecognitiveRequests.php @@ -50,8 +50,9 @@ protected function prepareForPrecognition(Request $request): void */ protected function appendVaryHeader(Request $request, Response $response): Response { + // Content negotiation can add separate Vary lines; retain all of them. return tap($response, fn () => $response->headers->set('Vary', implode(', ', array_filter([ - $response->headers->get('Vary'), + ...$response->headers->all('Vary'), 'Precognition', ])))); } diff --git a/src/inertia/src/Middleware.php b/src/inertia/src/Middleware.php index eb2e48dbba..c17c89c298 100644 --- a/src/inertia/src/Middleware.php +++ b/src/inertia/src/Middleware.php @@ -258,13 +258,11 @@ protected function isPartialReload(Request $request, Response $response): bool */ protected function addInertiaVaryHeader(Response $response): void { - foreach ($response->getVary() as $header) { - if (strcasecmp($header, Header::INERTIA) === 0) { - return; - } - } + // Keep case-insensitive matching without allocating a Collection on every response. + $vary = array_unique(array_filter($response->getVary())); + $present = array_any($vary, static fn (string $header): bool => strcasecmp($header, Header::INERTIA) === 0); - $response->setVary(Header::INERTIA, false); + $response->setVary(implode(', ', $present ? $vary : [...$vary, Header::INERTIA])); } /** diff --git a/tests/Inertia/MiddlewareTest.php b/tests/Inertia/MiddlewareTest.php index d85278e257..0c53ab79bd 100644 --- a/tests/Inertia/MiddlewareTest.php +++ b/tests/Inertia/MiddlewareTest.php @@ -6,6 +6,7 @@ use Hypervel\Contracts\Http\Kernel as HttpKernelContract; use Hypervel\Filesystem\Filesystem; +use Hypervel\Foundation\Http\Middleware\HandlePrecognitiveRequests; use Hypervel\Http\JsonResponse; use Hypervel\Http\RedirectResponse; use Hypervel\Http\Request; @@ -31,9 +32,7 @@ use Hypervel\Tests\Inertia\Fixtures\WithoutPreviousLocationMiddleware; use LogicException; use PHPUnit\Framework\Attributes\After; -use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Response as SymfonyResponse; -use Symfony\Component\HttpFoundation\StreamedResponse; class MiddlewareTest extends TestCase { @@ -99,17 +98,6 @@ public function testNoResponseMeansNoResponseForNonInertiaRequests(): void $this->assertTrue($fooCalled); } - public function testNonInertiaResponsesPreserveExistingVaryHeaders(): void - { - $request = Request::create('/'); - $response = new SymfonyResponse('content', 200, ['Vary' => 'Accept-Encoding']); - - $result = (new Middleware)->handle($request, fn () => $response); - - $this->assertSame($response, $result); - $this->assertSame(['Accept-Encoding', Header::INERTIA], $result->getVary()); - } - public function testInertiaVaryHeaderIsDeduplicatedCaseInsensitively(): void { $request = Request::create('/'); @@ -122,40 +110,44 @@ public function testInertiaVaryHeaderIsDeduplicatedCaseInsensitively(): void $this->assertSame(['x-inertia', 'Accept-Encoding'], $result->getVary()); } - public function testZeroContentIsNotTreatedAsAnEmptyResponse(): void + public function testAResponseBodyOfZeroIsNotTreatedAsEmptyForInertiaRequests(): void { - $request = Request::create('/'); - $request->headers->set(Header::INERTIA, 'true'); - $response = new SymfonyResponse('0'); + $original = response('0'); + Route::middleware(Middleware::class)->get('/', fn () => $original); - $result = (new Middleware)->handle($request, fn () => $response); + $response = $this->from('/foo')->get('/', ['X-Inertia' => 'true']); - $this->assertSame($response, $result); - $this->assertSame('0', $result->getContent()); + $response->assertOk(); + $this->assertSame($original, $response->baseResponse); + $this->assertSame('0', $response->getContent()); } - public function testStreamedResponsesAreNotTreatedAsEmptyResponses(): void + public function testStreamedResponsesAreNotTreatedAsEmptyForInertiaRequests(): void { - $request = Request::create('/'); - $request->headers->set(Header::INERTIA, 'true'); - $response = new StreamedResponse(fn () => print 'content'); + $original = response()->streamDownload(function (): void { + echo 'hello'; + }, 'hello.txt'); + Route::middleware(Middleware::class)->get('/', fn () => $original); - $result = (new Middleware)->handle($request, fn () => $response); + $response = $this->from('/foo')->get('/', ['X-Inertia' => 'true']); - $this->assertSame($response, $result); - $this->assertFalse($result->getContent()); + $response->assertOk(); + $response->assertDownload('hello.txt'); + $this->assertSame($original, $response->baseResponse); + $this->assertFalse($response->baseResponse->getContent()); } - public function testBinaryResponsesAreNotTreatedAsEmptyResponses(): void + public function testFileDownloadsAreNotTreatedAsEmptyForInertiaRequests(): void { - $request = Request::create('/'); - $request->headers->set(Header::INERTIA, 'true'); - $response = new BinaryFileResponse(__FILE__); + $original = response()->download(__DIR__ . '/Fixtures/ExamplePage.vue', 'example.vue'); + Route::middleware(Middleware::class)->get('/', fn () => $original); - $result = (new Middleware)->handle($request, fn () => $response); + $response = $this->from('/foo')->get('/', ['X-Inertia' => 'true']); - $this->assertSame($response, $result); - $this->assertFalse($result->getContent()); + $response->assertOk(); + $response->assertDownload('example.vue'); + $this->assertSame($original, $response->baseResponse); + $this->assertFalse($response->baseResponse->getContent()); } public function testTheVersionIsOptional(): void @@ -908,6 +900,63 @@ public function testDeferredCallbacksAreSkippedOnAFailedResponse(): void $this->assertFalse($called); } + public function testTheVaryHeaderIsSet(): void + { + Route::middleware(Middleware::class)->get('/', function () { + return Inertia::render('User/Edit'); + }); + + $response = $this->get('/'); + + $this->assertSame(['X-Inertia'], $response->headers->all('Vary')); + } + + public function testExistingVaryHeaderValuesArePreserved(): void + { + $original = null; + Route::middleware(Middleware::class)->get('/', function (Request $request) use (&$original) { + return $original = Inertia::render('User/Edit')->toResponse($request)->setVary('Accept-Language'); + }); + + $response = $this->get('/'); + + $this->assertSame($original, $response->baseResponse); + $this->assertSame(['Accept-Language, X-Inertia'], $response->headers->all('Vary')); + } + + public function testTheVaryHeaderIsNotDuplicated(): void + { + Route::middleware(Middleware::class)->get('/', function (Request $request) { + return Inertia::render('User/Edit')->toResponse($request)->setVary('X-Inertia'); + }); + + $response = $this->get('/'); + + $this->assertSame(['X-Inertia'], $response->headers->all('Vary')); + } + + public function testEmptyVaryHeaderValuesAreDiscarded(): void + { + Route::middleware(Middleware::class)->get('/', function (Request $request) { + return Inertia::render('User/Edit')->toResponse($request)->setVary(''); + }); + + $response = $this->get('/'); + + $this->assertSame(['X-Inertia'], $response->headers->all('Vary')); + } + + public function testExistingVaryHeaderValuesArePreservedOnPrecognitiveRoutes(): void + { + Route::middleware([HandlePrecognitiveRequests::class, Middleware::class])->get('/', function (Request $request) { + return Inertia::render('User/Edit')->toResponse($request)->setVary('Accept-Language'); + }); + + $response = $this->get('/'); + + $this->assertSame(['Accept-Language, X-Inertia, Precognition'], $response->headers->all('Vary')); + } + public function testMiddlewareRegistersSsrExceptPaths(): void { $middleware = new SsrExceptMiddleware; diff --git a/tests/Integration/Routing/PrecognitionTest.php b/tests/Integration/Routing/PrecognitionTest.php index 1eef45cb00..f6028ef3e0 100644 --- a/tests/Integration/Routing/PrecognitionTest.php +++ b/tests/Integration/Routing/PrecognitionTest.php @@ -20,6 +20,7 @@ use Hypervel\Support\Facades\Route; use Hypervel\Support\Facades\Validator; use Hypervel\Tests\Integration\Routing\RoutingTestCase; +use PHPUnit\Framework\Attributes\TestWith; function fail() { @@ -637,11 +638,13 @@ public function testItRetainsWildcardIdentityWhenValidatingSpecificInputs(): voi ]); } - public function testItAppendsAnAdditionalVaryHeaderInsteadOfReplacingAnyExistingVaryHeaders(): void + #[TestWith(['Foo', 'Foo, Precognition'])] + #[TestWith([['Foo', 'Prefer'], 'Foo, Prefer, Precognition'])] + public function testItAppendsAnAdditionalVaryHeaderInsteadOfReplacingAnyExistingVaryHeaders(string|array $vary, string $expected): void { - Route::get('test-route', function () { - precognitive(function ($bail) { - $bail(response('expected')->header('Vary', 'Foo')); + Route::get('test-route', function () use ($vary) { + precognitive(function ($bail) use ($vary) { + $bail(response('expected')->header('Vary', $vary)); fail(); }); fail(); @@ -649,7 +652,7 @@ public function testItAppendsAnAdditionalVaryHeaderInsteadOfReplacingAnyExisting $response = $this->get('test-route', ['Precognition' => 'true']); - $response->assertHeader('Vary', 'Foo, Precognition'); + $response->assertHeader('Vary', $expected); $response->assertHeaderMissing('Precognition-Success'); } From 8469e1d0fd8ef30e926cc469c87a44d3762b9002 Mon Sep 17 00:00:00 2001 From: Raj Siva-Rajah <5361908+binaryfire@users.noreply.github.com> Date: Sun, 1 Nov 2026 17:29:42 +0000 Subject: [PATCH 24/36] Invalidate cached Inertia SSR results when the page changes Port inertiajs/inertia-laravel #924 (6431f9a9778ddb3745c75a984947d0f8223b4ab1) from 3.x target 1e8706260b975c1c652dd886a53657e0fae2102d. Apply upstream's page comparison and SSR cache reset inside the existing coroutine-local state. Route response and directive page changes through that boundary, preserving cached results for unchanged pages and request isolation. Port the upstream cross-request test and cover a reproduced changed-page failure within one request. ResponseFactory, Directive, Component and CoroutineIsolation tests pass (104 tests, 228 assertions), along with formatting and source analysis. --- src/inertia/src/Directive.php | 8 +--- src/inertia/src/InertiaState.php | 23 +++++++++-- src/inertia/src/Response.php | 2 +- tests/Inertia/ResponseFactoryTest.php | 57 +++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/inertia/src/Directive.php b/src/inertia/src/Directive.php index ede3142979..7c718acf67 100644 --- a/src/inertia/src/Directive.php +++ b/src/inertia/src/Directive.php @@ -17,9 +17,7 @@ public static function compile(string $expression = ''): string $id = $id === '' ? 'app' : $id; $template = 'page = $page; - $__inertiaSsrResponse = $__inertiaState->dispatchSsr(); + $__inertiaSsrResponse = \Hypervel\Inertia\InertiaState::current()->setPage($page)->dispatchSsr(); if ($__inertiaSsrResponse) { echo $__inertiaSsrResponse->body; @@ -39,9 +37,7 @@ public static function compile(string $expression = ''): string public static function compileHead(string $expression = ''): string { $template = 'page = $page; - $__inertiaSsrResponse = $__inertiaState->dispatchSsr(); + $__inertiaSsrResponse = \Hypervel\Inertia\InertiaState::current()->setPage($page)->dispatchSsr(); if ($__inertiaSsrResponse) { echo $__inertiaSsrResponse->head; diff --git a/src/inertia/src/InertiaState.php b/src/inertia/src/InertiaState.php index bdfbe7d801..25878d50cb 100644 --- a/src/inertia/src/InertiaState.php +++ b/src/inertia/src/InertiaState.php @@ -74,12 +74,12 @@ class InertiaState implements ReplicableContext public array $page = []; /** - * The cached SSR response for the current request. + * The cached SSR response for the current page. */ public ?SsrResponse $ssrResponse = null; /** - * Whether the SSR gateway has been dispatched for this request. + * Whether the SSR gateway has been dispatched for the current page. */ public bool $ssrDispatched = false; @@ -126,7 +126,24 @@ public static function current(): self } /** - * Dispatch SSR if it has not already been dispatched for this request. + * Set the page data for the current request. + * + * @param array