diff --git a/.cursor/rules/laravel-boost.mdc b/.cursor/rules/laravel-boost.mdc
index 9464f06..6e21fa7 100644
--- a/.cursor/rules/laravel-boost.mdc
+++ b/.cursor/rules/laravel-boost.mdc
@@ -11,7 +11,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for
## Foundational Context
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
-- php - 8.3.24
+- php - 8.4.12
- laravel/framework (LARAVEL) - v12
- laravel/prompts (PROMPTS) - v0
- livewire/flux (FLUXUI_FREE) - v2
@@ -19,7 +19,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
- livewire/volt (VOLT) - v1
- larastan/larastan (LARASTAN) - v3
- laravel/pint (PINT) - v1
-- pestphp/pest (PEST) - v3
+- pestphp/pest (PEST) - v4
- tailwindcss (TAILWINDCSS) - v4
@@ -465,6 +465,53 @@ it('has emails', function (string $email) {
+=== pest/v4 rules ===
+
+## Pest 4
+
+- Pest v4 is a huge upgrade to Pest and offers: browser testing, smoke testing, visual regression testing, test sharding, and faster type coverage.
+- Browser testing is incredibly powerful and useful for this project.
+- Browser tests should live in `tests/Browser/`.
+- Use the `search-docs` tool for detailed guidance on utilizing these features.
+
+### Browser Testing
+- You can use Laravel features like `Event::fake()`, `assertAuthenticated()`, and model factories within Pest v4 browser tests, as well as `RefreshDatabase` (when needed) to ensure a clean state for each test.
+- Interact with the page (click, type, scroll, select, submit, drag-and-drop, touch gestures, etc.) when appropriate to complete the test.
+- If requested, test on multiple browsers (Chrome, Firefox, Safari).
+- If requested, test on different devices and viewports (like iPhone 14 Pro, tablets, or custom breakpoints).
+- Switch color schemes (light/dark mode) when appropriate.
+- Take screenshots or pause tests for debugging when appropriate.
+
+### Example Tests
+
+
+it('may reset the password', function () {
+ Notification::fake();
+
+ $this->actingAs(User::factory()->create());
+
+ $page = visit('/sign-in'); // Visit on a real browser...
+
+ $page->assertSee('Sign In')
+ ->assertNoJavascriptErrors() // or ->assertNoConsoleLogs()
+ ->click('Forgot Password?')
+ ->fill('email', 'nuno@laravel.com')
+ ->click('Send Reset Link')
+ ->assertSee('We have emailed your password reset link!')
+
+ Notification::assertSent(ResetPassword::class);
+});
+
+
+
+
+
+$pages = visit(['/', '/about', '/contact']);
+
+$pages->assertNoJavascriptErrors()->assertNoConsoleLogs();
+
+
+
=== tailwindcss/core rules ===
## Tailwind Core
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index a331541..be2748d 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for
## Foundational Context
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
-- php - 8.3.24
+- php - 8.4.12
- laravel/framework (LARAVEL) - v12
- laravel/prompts (PROMPTS) - v0
- livewire/flux (FLUXUI_FREE) - v2
@@ -16,7 +16,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
- livewire/volt (VOLT) - v1
- larastan/larastan (LARASTAN) - v3
- laravel/pint (PINT) - v1
-- pestphp/pest (PEST) - v3
+- pestphp/pest (PEST) - v4
- tailwindcss (TAILWINDCSS) - v4
@@ -462,6 +462,53 @@ it('has emails', function (string $email) {
+=== pest/v4 rules ===
+
+## Pest 4
+
+- Pest v4 is a huge upgrade to Pest and offers: browser testing, smoke testing, visual regression testing, test sharding, and faster type coverage.
+- Browser testing is incredibly powerful and useful for this project.
+- Browser tests should live in `tests/Browser/`.
+- Use the `search-docs` tool for detailed guidance on utilizing these features.
+
+### Browser Testing
+- You can use Laravel features like `Event::fake()`, `assertAuthenticated()`, and model factories within Pest v4 browser tests, as well as `RefreshDatabase` (when needed) to ensure a clean state for each test.
+- Interact with the page (click, type, scroll, select, submit, drag-and-drop, touch gestures, etc.) when appropriate to complete the test.
+- If requested, test on multiple browsers (Chrome, Firefox, Safari).
+- If requested, test on different devices and viewports (like iPhone 14 Pro, tablets, or custom breakpoints).
+- Switch color schemes (light/dark mode) when appropriate.
+- Take screenshots or pause tests for debugging when appropriate.
+
+### Example Tests
+
+
+it('may reset the password', function () {
+ Notification::fake();
+
+ $this->actingAs(User::factory()->create());
+
+ $page = visit('/sign-in'); // Visit on a real browser...
+
+ $page->assertSee('Sign In')
+ ->assertNoJavascriptErrors() // or ->assertNoConsoleLogs()
+ ->click('Forgot Password?')
+ ->fill('email', 'nuno@laravel.com')
+ ->click('Send Reset Link')
+ ->assertSee('We have emailed your password reset link!')
+
+ Notification::assertSent(ResetPassword::class);
+});
+
+
+
+
+
+$pages = visit(['/', '/about', '/contact']);
+
+$pages->assertNoJavascriptErrors()->assertNoConsoleLogs();
+
+
+
=== tailwindcss/core rules ===
## Tailwind Core
diff --git a/.junie/guidelines.md b/.junie/guidelines.md
index a331541..be2748d 100644
--- a/.junie/guidelines.md
+++ b/.junie/guidelines.md
@@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for
## Foundational Context
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
-- php - 8.3.24
+- php - 8.4.12
- laravel/framework (LARAVEL) - v12
- laravel/prompts (PROMPTS) - v0
- livewire/flux (FLUXUI_FREE) - v2
@@ -16,7 +16,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
- livewire/volt (VOLT) - v1
- larastan/larastan (LARASTAN) - v3
- laravel/pint (PINT) - v1
-- pestphp/pest (PEST) - v3
+- pestphp/pest (PEST) - v4
- tailwindcss (TAILWINDCSS) - v4
@@ -462,6 +462,53 @@ it('has emails', function (string $email) {
+=== pest/v4 rules ===
+
+## Pest 4
+
+- Pest v4 is a huge upgrade to Pest and offers: browser testing, smoke testing, visual regression testing, test sharding, and faster type coverage.
+- Browser testing is incredibly powerful and useful for this project.
+- Browser tests should live in `tests/Browser/`.
+- Use the `search-docs` tool for detailed guidance on utilizing these features.
+
+### Browser Testing
+- You can use Laravel features like `Event::fake()`, `assertAuthenticated()`, and model factories within Pest v4 browser tests, as well as `RefreshDatabase` (when needed) to ensure a clean state for each test.
+- Interact with the page (click, type, scroll, select, submit, drag-and-drop, touch gestures, etc.) when appropriate to complete the test.
+- If requested, test on multiple browsers (Chrome, Firefox, Safari).
+- If requested, test on different devices and viewports (like iPhone 14 Pro, tablets, or custom breakpoints).
+- Switch color schemes (light/dark mode) when appropriate.
+- Take screenshots or pause tests for debugging when appropriate.
+
+### Example Tests
+
+
+it('may reset the password', function () {
+ Notification::fake();
+
+ $this->actingAs(User::factory()->create());
+
+ $page = visit('/sign-in'); // Visit on a real browser...
+
+ $page->assertSee('Sign In')
+ ->assertNoJavascriptErrors() // or ->assertNoConsoleLogs()
+ ->click('Forgot Password?')
+ ->fill('email', 'nuno@laravel.com')
+ ->click('Send Reset Link')
+ ->assertSee('We have emailed your password reset link!')
+
+ Notification::assertSent(ResetPassword::class);
+});
+
+
+
+
+
+$pages = visit(['/', '/about', '/contact']);
+
+$pages->assertNoJavascriptErrors()->assertNoConsoleLogs();
+
+
+
=== tailwindcss/core rules ===
## Tailwind Core
diff --git a/CLAUDE.md b/CLAUDE.md
index a331541..be2748d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -8,7 +8,7 @@ The Laravel Boost guidelines are specifically curated by Laravel maintainers for
## Foundational Context
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
-- php - 8.3.24
+- php - 8.4.12
- laravel/framework (LARAVEL) - v12
- laravel/prompts (PROMPTS) - v0
- livewire/flux (FLUXUI_FREE) - v2
@@ -16,7 +16,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
- livewire/volt (VOLT) - v1
- larastan/larastan (LARASTAN) - v3
- laravel/pint (PINT) - v1
-- pestphp/pest (PEST) - v3
+- pestphp/pest (PEST) - v4
- tailwindcss (TAILWINDCSS) - v4
@@ -462,6 +462,53 @@ it('has emails', function (string $email) {
+=== pest/v4 rules ===
+
+## Pest 4
+
+- Pest v4 is a huge upgrade to Pest and offers: browser testing, smoke testing, visual regression testing, test sharding, and faster type coverage.
+- Browser testing is incredibly powerful and useful for this project.
+- Browser tests should live in `tests/Browser/`.
+- Use the `search-docs` tool for detailed guidance on utilizing these features.
+
+### Browser Testing
+- You can use Laravel features like `Event::fake()`, `assertAuthenticated()`, and model factories within Pest v4 browser tests, as well as `RefreshDatabase` (when needed) to ensure a clean state for each test.
+- Interact with the page (click, type, scroll, select, submit, drag-and-drop, touch gestures, etc.) when appropriate to complete the test.
+- If requested, test on multiple browsers (Chrome, Firefox, Safari).
+- If requested, test on different devices and viewports (like iPhone 14 Pro, tablets, or custom breakpoints).
+- Switch color schemes (light/dark mode) when appropriate.
+- Take screenshots or pause tests for debugging when appropriate.
+
+### Example Tests
+
+
+it('may reset the password', function () {
+ Notification::fake();
+
+ $this->actingAs(User::factory()->create());
+
+ $page = visit('/sign-in'); // Visit on a real browser...
+
+ $page->assertSee('Sign In')
+ ->assertNoJavascriptErrors() // or ->assertNoConsoleLogs()
+ ->click('Forgot Password?')
+ ->fill('email', 'nuno@laravel.com')
+ ->click('Send Reset Link')
+ ->assertSee('We have emailed your password reset link!')
+
+ Notification::assertSent(ResetPassword::class);
+});
+
+
+
+
+
+$pages = visit(['/', '/about', '/contact']);
+
+$pages->assertNoJavascriptErrors()->assertNoConsoleLogs();
+
+
+
=== tailwindcss/core rules ===
## Tailwind Core
diff --git a/app/Models/Plugin.php b/app/Models/Plugin.php
index 7290381..3079ab7 100644
--- a/app/Models/Plugin.php
+++ b/app/Models/Plugin.php
@@ -236,11 +236,11 @@ class Plugin extends Model
$template = preg_replace_callback(
'/{%\s*for\s+(\w+)\s+in\s+([^|]+)\s*\|\s*([^}]+)%}/',
function ($matches) {
- $variableName = trim($matches[1]);
- $collection = trim($matches[2]);
- $filter = trim($matches[3]);
- $tempVarName = '_temp_' . uniqid();
-
+ $variableName = mb_trim($matches[1]);
+ $collection = mb_trim($matches[2]);
+ $filter = mb_trim($matches[3]);
+ $tempVarName = '_temp_'.uniqid();
+
return "{% assign {$tempVarName} = {$collection} | {$filter} %}{% for {$variableName} in {$tempVarName} %}";
},
$template
diff --git a/app/Services/PluginImportService.php b/app/Services/PluginImportService.php
index dbd8ec8..29b5688 100644
--- a/app/Services/PluginImportService.php
+++ b/app/Services/PluginImportService.php
@@ -7,6 +7,7 @@ use App\Models\User;
use Exception;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;
+use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
@@ -48,6 +49,130 @@ class PluginImportService
// Find the required files (settings.yml and full.liquid/full.blade.php)
$filePaths = $this->findRequiredFiles($tempDir);
+ // Validate that we found the required files
+ if (! $filePaths['settingsYamlPath'] || ! $filePaths['fullLiquidPath']) {
+ throw new Exception('Invalid ZIP structure. Required files settings.yml and full.liquid are missing.'); // full.blade.php
+ }
+
+ // Parse settings.yml
+ $settingsYaml = File::get($filePaths['settingsYamlPath']);
+ $settings = Yaml::parse($settingsYaml);
+
+ // Read full.liquid content
+ $fullLiquid = File::get($filePaths['fullLiquidPath']);
+
+ // Prepend shared.liquid content if available
+ if ($filePaths['sharedLiquidPath'] && File::exists($filePaths['sharedLiquidPath'])) {
+ $sharedLiquid = File::get($filePaths['sharedLiquidPath']);
+ $fullLiquid = $sharedLiquid."\n".$fullLiquid;
+ }
+
+ $fullLiquid = '
'."\n".$fullLiquid."\n".'
';
+
+ // Check if the file ends with .liquid to set markup language
+ $markupLanguage = 'blade';
+ if (pathinfo($filePaths['fullLiquidPath'], PATHINFO_EXTENSION) === 'liquid') {
+ $markupLanguage = 'liquid';
+ }
+
+ // Ensure custom_fields is properly formatted
+ if (! isset($settings['custom_fields']) || ! is_array($settings['custom_fields'])) {
+ $settings['custom_fields'] = [];
+ }
+
+ // Create configuration template with the custom fields
+ $configurationTemplate = [
+ 'custom_fields' => $settings['custom_fields'],
+ ];
+
+ $plugin_updated = isset($settings['id'])
+ && Plugin::where('user_id', $user->id)->where('trmnlp_id', $settings['id'])->exists();
+ // Create a new plugin
+ $plugin = Plugin::updateOrCreate(
+ [
+ 'user_id' => $user->id, 'trmnlp_id' => $settings['id'] ?? Uuid::v7(),
+ ],
+ [
+ 'user_id' => $user->id,
+ 'name' => $settings['name'] ?? 'Imported Plugin',
+ 'trmnlp_id' => $settings['id'] ?? Uuid::v7(),
+ 'data_stale_minutes' => $settings['refresh_interval'] ?? 15,
+ 'data_strategy' => $settings['strategy'] ?? 'static',
+ 'polling_url' => $settings['polling_url'] ?? null,
+ 'polling_verb' => $settings['polling_verb'] ?? 'get',
+ 'polling_header' => isset($settings['polling_headers'])
+ ? str_replace('=', ':', $settings['polling_headers'])
+ : null,
+ 'polling_body' => $settings['polling_body'] ?? null,
+ 'markup_language' => $markupLanguage,
+ 'render_markup' => $fullLiquid,
+ 'configuration_template' => $configurationTemplate,
+ 'data_payload' => json_decode($settings['static_data'] ?? '{}', true),
+ ]);
+
+ if (! $plugin_updated) {
+ // Extract default values from custom_fields and populate configuration
+ $configuration = [];
+ foreach ($settings['custom_fields'] as $field) {
+ if (isset($field['keyname']) && isset($field['default'])) {
+ $configuration[$field['keyname']] = $field['default'];
+ }
+ }
+ // set only if plugin is new
+ $plugin->update([
+ 'configuration' => $configuration,
+ ]);
+ }
+ $plugin['trmnlp_yaml'] = $settingsYaml;
+
+ return $plugin;
+
+ } finally {
+ // Clean up temporary directory
+ Storage::deleteDirectory($tempDirName);
+ }
+ }
+
+ /**
+ * Import a plugin from a ZIP URL
+ *
+ * @param string $zipUrl The URL to the ZIP file
+ * @param User $user The user importing the plugin
+ * @return Plugin The created plugin instance
+ *
+ * @throws Exception If the ZIP file is invalid or required files are missing
+ */
+ public function importFromUrl(string $zipUrl, User $user): Plugin
+ {
+ // Download the ZIP file
+ $response = Http::timeout(60)->get($zipUrl);
+
+ if (! $response->successful()) {
+ throw new Exception('Could not download the ZIP file from the provided URL.');
+ }
+
+ // Create a temporary file
+ $tempDirName = 'temp/'.uniqid('plugin_import_', true);
+ Storage::makeDirectory($tempDirName);
+ $tempDir = Storage::path($tempDirName);
+ $zipPath = $tempDir.'/plugin.zip';
+
+ // Save the downloaded content to a temporary file
+ File::put($zipPath, $response->body());
+
+ try {
+ // Extract the ZIP file using ZipArchive
+ $zip = new ZipArchive();
+ if ($zip->open($zipPath) !== true) {
+ throw new Exception('Could not open the downloaded ZIP file.');
+ }
+
+ $zip->extractTo($tempDir);
+ $zip->close();
+
+ // Find the required files (settings.yml and full.liquid/full.blade.php)
+ $filePaths = $this->findRequiredFiles($tempDir);
+
// Validate that we found the required files
if (! $filePaths['settingsYamlPath'] || ! $filePaths['fullLiquidPath']) {
throw new Exception('Invalid ZIP structure. Required files settings.yml and full.liquid/full.blade.php are missing.');
diff --git a/composer.lock b/composer.lock
index 79f70e1..2ad26ef 100644
--- a/composer.lock
+++ b/composer.lock
@@ -62,16 +62,16 @@
},
{
"name": "aws/aws-sdk-php",
- "version": "3.356.5",
+ "version": "3.356.8",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "5872ccb5100c4afb0dae3db0bd46636f63ae8147"
+ "reference": "3efa8c62c11fedb17b90f60b2d3a9f815b406e63"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5872ccb5100c4afb0dae3db0bd46636f63ae8147",
- "reference": "5872ccb5100c4afb0dae3db0bd46636f63ae8147",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3efa8c62c11fedb17b90f60b2d3a9f815b406e63",
+ "reference": "3efa8c62c11fedb17b90f60b2d3a9f815b406e63",
"shasum": ""
},
"require": {
@@ -153,9 +153,9 @@
"support": {
"forum": "https://github.com/aws/aws-sdk-php/discussions",
"issues": "https://github.com/aws/aws-sdk-php/issues",
- "source": "https://github.com/aws/aws-sdk-php/tree/3.356.5"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.356.8"
},
- "time": "2025-08-26T18:05:04+00:00"
+ "time": "2025-08-29T18:06:18+00:00"
},
{
"name": "bnussbau/laravel-trmnl-blade",
@@ -1691,16 +1691,16 @@
},
{
"name": "laravel/framework",
- "version": "v12.26.2",
+ "version": "v12.26.4",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "56c5fc46cfb1005d0aaa82c7592d63edb776a787"
+ "reference": "085a367a32ba86fcfa647bfc796098ae6f795b09"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/56c5fc46cfb1005d0aaa82c7592d63edb776a787",
- "reference": "56c5fc46cfb1005d0aaa82c7592d63edb776a787",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/085a367a32ba86fcfa647bfc796098ae6f795b09",
+ "reference": "085a367a32ba86fcfa647bfc796098ae6f795b09",
"shasum": ""
},
"require": {
@@ -1740,8 +1740,8 @@
"symfony/http-kernel": "^7.2.0",
"symfony/mailer": "^7.2.0",
"symfony/mime": "^7.2.0",
- "symfony/polyfill-php83": "^1.31",
- "symfony/polyfill-php84": "^1.31",
+ "symfony/polyfill-php83": "^1.33",
+ "symfony/polyfill-php84": "^1.33",
"symfony/polyfill-php85": "^1.33",
"symfony/process": "^7.2.0",
"symfony/routing": "^7.2.0",
@@ -1810,7 +1810,7 @@
"league/flysystem-read-only": "^3.25.1",
"league/flysystem-sftp-v3": "^3.25.1",
"mockery/mockery": "^1.6.10",
- "orchestra/testbench-core": "^10.6.0",
+ "orchestra/testbench-core": "^10.6.3",
"pda/pheanstalk": "^5.0.6|^7.0.0",
"php-http/discovery": "^1.15",
"phpstan/phpstan": "^2.0",
@@ -1904,7 +1904,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
- "time": "2025-08-26T18:04:56+00:00"
+ "time": "2025-08-29T14:15:53+00:00"
},
{
"name": "laravel/prompts",
@@ -4973,16 +4973,16 @@
},
{
"name": "symfony/console",
- "version": "v7.3.2",
+ "version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1"
+ "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1",
- "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1",
+ "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
+ "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
"shasum": ""
},
"require": {
@@ -5047,7 +5047,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v7.3.2"
+ "source": "https://github.com/symfony/console/tree/v7.3.3"
},
"funding": [
{
@@ -5067,7 +5067,7 @@
"type": "tidelift"
}
],
- "time": "2025-07-30T17:13:41+00:00"
+ "time": "2025-08-25T06:35:40+00:00"
},
{
"name": "symfony/css-selector",
@@ -5284,16 +5284,16 @@
},
{
"name": "symfony/event-dispatcher",
- "version": "v7.3.0",
+ "version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "497f73ac996a598c92409b44ac43b6690c4f666d"
+ "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d",
- "reference": "497f73ac996a598c92409b44ac43b6690c4f666d",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191",
+ "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191",
"shasum": ""
},
"require": {
@@ -5344,7 +5344,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.0"
+ "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.3"
},
"funding": [
{
@@ -5355,12 +5355,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-04-22T09:11:45+00:00"
+ "time": "2025-08-13T11:49:31+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
@@ -5508,16 +5512,16 @@
},
{
"name": "symfony/http-foundation",
- "version": "v7.3.2",
+ "version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6"
+ "reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6877c122b3a6cc3695849622720054f6e6fa5fa6",
- "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/7475561ec27020196c49bb7c4f178d33d7d3dc00",
+ "reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00",
"shasum": ""
},
"require": {
@@ -5567,7 +5571,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v7.3.2"
+ "source": "https://github.com/symfony/http-foundation/tree/v7.3.3"
},
"funding": [
{
@@ -5587,20 +5591,20 @@
"type": "tidelift"
}
],
- "time": "2025-07-10T08:47:49+00:00"
+ "time": "2025-08-20T08:04:18+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v7.3.2",
+ "version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c"
+ "reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6ecc895559ec0097e221ed2fd5eb44d5fede083c",
- "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/72c304de37e1a1cec6d5d12b81187ebd4850a17b",
+ "reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b",
"shasum": ""
},
"require": {
@@ -5685,7 +5689,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-kernel/tree/v7.3.2"
+ "source": "https://github.com/symfony/http-kernel/tree/v7.3.3"
},
"funding": [
{
@@ -5705,20 +5709,20 @@
"type": "tidelift"
}
],
- "time": "2025-07-31T10:45:04+00:00"
+ "time": "2025-08-29T08:23:45+00:00"
},
{
"name": "symfony/mailer",
- "version": "v7.3.2",
+ "version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/mailer.git",
- "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b"
+ "reference": "a32f3f45f1990db8c4341d5122a7d3a381c7e575"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mailer/zipball/d43e84d9522345f96ad6283d5dfccc8c1cfc299b",
- "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b",
+ "url": "https://api.github.com/repos/symfony/mailer/zipball/a32f3f45f1990db8c4341d5122a7d3a381c7e575",
+ "reference": "a32f3f45f1990db8c4341d5122a7d3a381c7e575",
"shasum": ""
},
"require": {
@@ -5769,7 +5773,7 @@
"description": "Helps sending emails",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/mailer/tree/v7.3.2"
+ "source": "https://github.com/symfony/mailer/tree/v7.3.3"
},
"funding": [
{
@@ -5789,7 +5793,7 @@
"type": "tidelift"
}
],
- "time": "2025-07-15T11:36:08+00:00"
+ "time": "2025-08-13T11:49:31+00:00"
},
{
"name": "symfony/mime",
@@ -6710,16 +6714,16 @@
},
{
"name": "symfony/process",
- "version": "v7.3.0",
+ "version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af"
+ "reference": "32241012d521e2e8a9d713adb0812bb773b907f1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af",
- "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af",
+ "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1",
+ "reference": "32241012d521e2e8a9d713adb0812bb773b907f1",
"shasum": ""
},
"require": {
@@ -6751,7 +6755,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v7.3.0"
+ "source": "https://github.com/symfony/process/tree/v7.3.3"
},
"funding": [
{
@@ -6762,12 +6766,16 @@
"url": "https://github.com/fabpot",
"type": "github"
},
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
- "time": "2025-04-17T09:11:12+00:00"
+ "time": "2025-08-18T09:42:54+00:00"
},
{
"name": "symfony/routing",
@@ -6939,16 +6947,16 @@
},
{
"name": "symfony/string",
- "version": "v7.3.2",
+ "version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca"
+ "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca",
- "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca",
+ "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
+ "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
"shasum": ""
},
"require": {
@@ -7006,7 +7014,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v7.3.2"
+ "source": "https://github.com/symfony/string/tree/v7.3.3"
},
"funding": [
{
@@ -7026,20 +7034,20 @@
"type": "tidelift"
}
],
- "time": "2025-07-10T08:47:49+00:00"
+ "time": "2025-08-25T06:35:40+00:00"
},
{
"name": "symfony/translation",
- "version": "v7.3.2",
+ "version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "81b48f4daa96272efcce9c7a6c4b58e629df3c90"
+ "reference": "e0837b4cbcef63c754d89a4806575cada743a38d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/81b48f4daa96272efcce9c7a6c4b58e629df3c90",
- "reference": "81b48f4daa96272efcce9c7a6c4b58e629df3c90",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/e0837b4cbcef63c754d89a4806575cada743a38d",
+ "reference": "e0837b4cbcef63c754d89a4806575cada743a38d",
"shasum": ""
},
"require": {
@@ -7106,7 +7114,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/translation/tree/v7.3.2"
+ "source": "https://github.com/symfony/translation/tree/v7.3.3"
},
"funding": [
{
@@ -7126,7 +7134,7 @@
"type": "tidelift"
}
],
- "time": "2025-07-30T17:31:46+00:00"
+ "time": "2025-08-01T21:02:37+00:00"
},
{
"name": "symfony/translation-contracts",
@@ -7282,16 +7290,16 @@
},
{
"name": "symfony/var-dumper",
- "version": "v7.3.2",
+ "version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "53205bea27450dc5c65377518b3275e126d45e75"
+ "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/53205bea27450dc5c65377518b3275e126d45e75",
- "reference": "53205bea27450dc5c65377518b3275e126d45e75",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/34d8d4c4b9597347306d1ec8eb4e1319b1e6986f",
+ "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f",
"shasum": ""
},
"require": {
@@ -7345,7 +7353,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v7.3.2"
+ "source": "https://github.com/symfony/var-dumper/tree/v7.3.3"
},
"funding": [
{
@@ -7365,20 +7373,20 @@
"type": "tidelift"
}
],
- "time": "2025-07-29T20:02:46+00:00"
+ "time": "2025-08-13T11:49:31+00:00"
},
{
"name": "symfony/var-exporter",
- "version": "v7.3.2",
+ "version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-exporter.git",
- "reference": "05b3e90654c097817325d6abd284f7938b05f467"
+ "reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-exporter/zipball/05b3e90654c097817325d6abd284f7938b05f467",
- "reference": "05b3e90654c097817325d6abd284f7938b05f467",
+ "url": "https://api.github.com/repos/symfony/var-exporter/zipball/d4dfcd2a822cbedd7612eb6fbd260e46f87b7137",
+ "reference": "d4dfcd2a822cbedd7612eb6fbd260e46f87b7137",
"shasum": ""
},
"require": {
@@ -7426,7 +7434,7 @@
"serialize"
],
"support": {
- "source": "https://github.com/symfony/var-exporter/tree/v7.3.2"
+ "source": "https://github.com/symfony/var-exporter/tree/v7.3.3"
},
"funding": [
{
@@ -7446,20 +7454,20 @@
"type": "tidelift"
}
],
- "time": "2025-07-10T08:47:49+00:00"
+ "time": "2025-08-18T13:10:53+00:00"
},
{
"name": "symfony/yaml",
- "version": "v7.3.2",
+ "version": "v7.3.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30"
+ "reference": "d4f4a66866fe2451f61296924767280ab5732d9d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/b8d7d868da9eb0919e99c8830431ea087d6aae30",
- "reference": "b8d7d868da9eb0919e99c8830431ea087d6aae30",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/d4f4a66866fe2451f61296924767280ab5732d9d",
+ "reference": "d4f4a66866fe2451f61296924767280ab5732d9d",
"shasum": ""
},
"require": {
@@ -7502,7 +7510,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v7.3.2"
+ "source": "https://github.com/symfony/yaml/tree/v7.3.3"
},
"funding": [
{
@@ -7522,7 +7530,7 @@
"type": "tidelift"
}
],
- "time": "2025-07-10T08:47:49+00:00"
+ "time": "2025-08-27T11:34:33+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
@@ -7885,16 +7893,16 @@
"packages-dev": [
{
"name": "brianium/paratest",
- "version": "v7.11.2",
+ "version": "v7.12.0",
"source": {
"type": "git",
"url": "https://github.com/paratestphp/paratest.git",
- "reference": "521aa381c212816d0dc2f04f1532a5831969cb5e"
+ "reference": "6a34ddb12a3bd5bd07d831ce95f111087f3bcbd8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/paratestphp/paratest/zipball/521aa381c212816d0dc2f04f1532a5831969cb5e",
- "reference": "521aa381c212816d0dc2f04f1532a5831969cb5e",
+ "url": "https://api.github.com/repos/paratestphp/paratest/zipball/6a34ddb12a3bd5bd07d831ce95f111087f3bcbd8",
+ "reference": "6a34ddb12a3bd5bd07d831ce95f111087f3bcbd8",
"shasum": ""
},
"require": {
@@ -7904,11 +7912,11 @@
"ext-simplexml": "*",
"fidry/cpu-core-counter": "^1.3.0",
"jean85/pretty-package-versions": "^2.1.1",
- "php": "~8.3.0 || ~8.4.0",
+ "php": "~8.3.0 || ~8.4.0 || ~8.5.0",
"phpunit/php-code-coverage": "^12.3.2",
"phpunit/php-file-iterator": "^6",
"phpunit/php-timer": "^8",
- "phpunit/phpunit": "^12.3.5",
+ "phpunit/phpunit": "^12.3.6",
"sebastian/environment": "^8.0.3",
"symfony/console": "^6.4.20 || ^7.3.2",
"symfony/process": "^6.4.20 || ^7.3.0"
@@ -7963,7 +7971,7 @@
],
"support": {
"issues": "https://github.com/paratestphp/paratest/issues",
- "source": "https://github.com/paratestphp/paratest/tree/v7.11.2"
+ "source": "https://github.com/paratestphp/paratest/tree/v7.12.0"
},
"funding": [
{
@@ -7975,7 +7983,7 @@
"type": "paypal"
}
],
- "time": "2025-08-19T09:24:27+00:00"
+ "time": "2025-08-29T05:28:31+00:00"
},
{
"name": "doctrine/deprecations",
@@ -8463,16 +8471,16 @@
},
{
"name": "laravel/boost",
- "version": "v1.0.18",
+ "version": "v1.0.20",
"source": {
"type": "git",
"url": "https://github.com/laravel/boost.git",
- "reference": "df2a62b5864759ea8cce8a4b7575b657e9c7d4ab"
+ "reference": "c2ac67ce42c39ffe6c3c073c9202d54a96eaa5b5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/boost/zipball/df2a62b5864759ea8cce8a4b7575b657e9c7d4ab",
- "reference": "df2a62b5864759ea8cce8a4b7575b657e9c7d4ab",
+ "url": "https://api.github.com/repos/laravel/boost/zipball/c2ac67ce42c39ffe6c3c073c9202d54a96eaa5b5",
+ "reference": "c2ac67ce42c39ffe6c3c073c9202d54a96eaa5b5",
"shasum": ""
},
"require": {
@@ -8481,13 +8489,13 @@
"illuminate/contracts": "^10.0|^11.0|^12.0",
"illuminate/routing": "^10.0|^11.0|^12.0",
"illuminate/support": "^10.0|^11.0|^12.0",
- "laravel/mcp": "^0.1.0",
+ "laravel/mcp": "^0.1.1",
"laravel/prompts": "^0.1.9|^0.3",
- "laravel/roster": "^0.2",
- "php": "^8.1|^8.2"
+ "laravel/roster": "^0.2.4",
+ "php": "^8.1"
},
"require-dev": {
- "laravel/pint": "^1.14|^1.23",
+ "laravel/pint": "^1.14",
"mockery/mockery": "^1.6",
"orchestra/testbench": "^8.22.0|^9.0|^10.0",
"pestphp/pest": "^2.0|^3.0",
@@ -8524,7 +8532,7 @@
"issues": "https://github.com/laravel/boost/issues",
"source": "https://github.com/laravel/boost"
},
- "time": "2025-08-16T09:10:03+00:00"
+ "time": "2025-08-28T14:46:17+00:00"
},
{
"name": "laravel/mcp",
@@ -8740,16 +8748,16 @@
},
{
"name": "laravel/roster",
- "version": "v0.2.3",
+ "version": "v0.2.5",
"source": {
"type": "git",
"url": "https://github.com/laravel/roster.git",
- "reference": "caeed7609b02c00c3f1efec52812d8d87c5d4096"
+ "reference": "0252fa419733c61b3ebeba8e4e2b9ad2a63f3a17"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/roster/zipball/caeed7609b02c00c3f1efec52812d8d87c5d4096",
- "reference": "caeed7609b02c00c3f1efec52812d8d87c5d4096",
+ "url": "https://api.github.com/repos/laravel/roster/zipball/0252fa419733c61b3ebeba8e4e2b9ad2a63f3a17",
+ "reference": "0252fa419733c61b3ebeba8e4e2b9ad2a63f3a17",
"shasum": ""
},
"require": {
@@ -8797,20 +8805,20 @@
"issues": "https://github.com/laravel/roster/issues",
"source": "https://github.com/laravel/roster"
},
- "time": "2025-08-13T15:00:25+00:00"
+ "time": "2025-08-29T07:47:42+00:00"
},
{
"name": "laravel/sail",
- "version": "v1.44.0",
+ "version": "v1.45.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/sail.git",
- "reference": "a09097bd2a8a38e23ac472fa6a6cf5b0d1c1d3fe"
+ "reference": "019a2933ff4a9199f098d4259713f9bc266a874e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/sail/zipball/a09097bd2a8a38e23ac472fa6a6cf5b0d1c1d3fe",
- "reference": "a09097bd2a8a38e23ac472fa6a6cf5b0d1c1d3fe",
+ "url": "https://api.github.com/repos/laravel/sail/zipball/019a2933ff4a9199f098d4259713f9bc266a874e",
+ "reference": "019a2933ff4a9199f098d4259713f9bc266a874e",
"shasum": ""
},
"require": {
@@ -8860,7 +8868,7 @@
"issues": "https://github.com/laravel/sail/issues",
"source": "https://github.com/laravel/sail"
},
- "time": "2025-07-04T16:17:06+00:00"
+ "time": "2025-08-25T19:28:31+00:00"
},
{
"name": "mockery/mockery",
@@ -9106,16 +9114,16 @@
},
{
"name": "pestphp/pest",
- "version": "v4.0.3",
+ "version": "v4.0.4",
"source": {
"type": "git",
"url": "https://github.com/pestphp/pest.git",
- "reference": "e54e4a0178889209a928f7bee63286149d4eb707"
+ "reference": "47fb1d77631d608022cc7af96cac90ac741c8394"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pestphp/pest/zipball/e54e4a0178889209a928f7bee63286149d4eb707",
- "reference": "e54e4a0178889209a928f7bee63286149d4eb707",
+ "url": "https://api.github.com/repos/pestphp/pest/zipball/47fb1d77631d608022cc7af96cac90ac741c8394",
+ "reference": "47fb1d77631d608022cc7af96cac90ac741c8394",
"shasum": ""
},
"require": {
@@ -9127,12 +9135,12 @@
"pestphp/pest-plugin-mutate": "^4.0.1",
"pestphp/pest-plugin-profanity": "^4.0.1",
"php": "^8.3.0",
- "phpunit/phpunit": "^12.3.6",
+ "phpunit/phpunit": "^12.3.7",
"symfony/process": "^7.3.0"
},
"conflict": {
"filp/whoops": "<2.18.3",
- "phpunit/phpunit": ">12.3.6",
+ "phpunit/phpunit": ">12.3.7",
"sebastian/exporter": "<7.0.0",
"webmozart/assert": "<1.11.0"
},
@@ -9206,7 +9214,7 @@
],
"support": {
"issues": "https://github.com/pestphp/pest/issues",
- "source": "https://github.com/pestphp/pest/tree/v4.0.3"
+ "source": "https://github.com/pestphp/pest/tree/v4.0.4"
},
"funding": [
{
@@ -9218,7 +9226,7 @@
"type": "github"
}
],
- "time": "2025-08-24T14:17:23+00:00"
+ "time": "2025-08-28T18:19:42+00:00"
},
{
"name": "pestphp/pest-plugin",
@@ -9930,16 +9938,16 @@
},
{
"name": "phpstan/phpdoc-parser",
- "version": "2.2.0",
+ "version": "2.3.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
- "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8"
+ "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b9e61a61e39e02dd90944e9115241c7f7e76bfd8",
- "reference": "b9e61a61e39e02dd90944e9115241c7f7e76bfd8",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/1e0cd5370df5dd2e556a36b9c62f62e555870495",
+ "reference": "1e0cd5370df5dd2e556a36b9c62f62e555870495",
"shasum": ""
},
"require": {
@@ -9971,9 +9979,9 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
- "source": "https://github.com/phpstan/phpdoc-parser/tree/2.2.0"
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.0"
},
- "time": "2025-07-13T07:04:09+00:00"
+ "time": "2025-08-30T15:50:23+00:00"
},
{
"name": "phpstan/phpstan",
@@ -10035,34 +10043,34 @@
},
{
"name": "phpunit/php-code-coverage",
- "version": "12.3.2",
+ "version": "12.3.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "086553c5b2e0e1e20293d782d788ab768202b621"
+ "reference": "96dc0466673e215bf5536301039017f03cd45c6b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/086553c5b2e0e1e20293d782d788ab768202b621",
- "reference": "086553c5b2e0e1e20293d782d788ab768202b621",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/96dc0466673e215bf5536301039017f03cd45c6b",
+ "reference": "96dc0466673e215bf5536301039017f03cd45c6b",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
- "nikic/php-parser": "^5.4.0",
+ "nikic/php-parser": "^5.6.1",
"php": ">=8.3",
"phpunit/php-file-iterator": "^6.0",
"phpunit/php-text-template": "^5.0",
"sebastian/complexity": "^5.0",
- "sebastian/environment": "^8.0",
+ "sebastian/environment": "^8.0.3",
"sebastian/lines-of-code": "^4.0",
"sebastian/version": "^6.0",
"theseer/tokenizer": "^1.2.3"
},
"require-dev": {
- "phpunit/phpunit": "^12.1"
+ "phpunit/phpunit": "^12.3.7"
},
"suggest": {
"ext-pcov": "PHP extension that provides line coverage",
@@ -10100,7 +10108,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.2"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.3.5"
},
"funding": [
{
@@ -10120,7 +10128,7 @@
"type": "tidelift"
}
],
- "time": "2025-07-29T06:19:24+00:00"
+ "time": "2025-09-01T08:07:42+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -10369,16 +10377,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "12.3.6",
+ "version": "12.3.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "a2cab3224f687150ac2f3cc13d99b64ba1e1d088"
+ "reference": "b8fa997c49682979ad6bfaa0d7fb25f54954965e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2cab3224f687150ac2f3cc13d99b64ba1e1d088",
- "reference": "a2cab3224f687150ac2f3cc13d99b64ba1e1d088",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b8fa997c49682979ad6bfaa0d7fb25f54954965e",
+ "reference": "b8fa997c49682979ad6bfaa0d7fb25f54954965e",
"shasum": ""
},
"require": {
@@ -10392,7 +10400,7 @@
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.3",
- "phpunit/php-code-coverage": "^12.3.2",
+ "phpunit/php-code-coverage": "^12.3.3",
"phpunit/php-file-iterator": "^6.0.0",
"phpunit/php-invoker": "^6.0.0",
"phpunit/php-text-template": "^5.0.0",
@@ -10446,7 +10454,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/12.3.6"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/12.3.7"
},
"funding": [
{
@@ -10470,7 +10478,7 @@
"type": "tidelift"
}
],
- "time": "2025-08-20T14:43:23+00:00"
+ "time": "2025-08-28T05:15:46+00:00"
},
{
"name": "sebastian/cli-parser",
@@ -10902,16 +10910,16 @@
},
{
"name": "sebastian/global-state",
- "version": "8.0.0",
+ "version": "8.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
- "reference": "570a2aeb26d40f057af686d63c4e99b075fb6cbc"
+ "reference": "ef1377171613d09edd25b7816f05be8313f9115d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/570a2aeb26d40f057af686d63c4e99b075fb6cbc",
- "reference": "570a2aeb26d40f057af686d63c4e99b075fb6cbc",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d",
+ "reference": "ef1377171613d09edd25b7816f05be8313f9115d",
"shasum": ""
},
"require": {
@@ -10952,15 +10960,27 @@
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
"security": "https://github.com/sebastianbergmann/global-state/security/policy",
- "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.0"
+ "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.2"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
+ },
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state",
+ "type": "tidelift"
}
],
- "time": "2025-02-07T04:56:59+00:00"
+ "time": "2025-08-29T11:29:25+00:00"
},
{
"name": "sebastian/lines-of-code",
diff --git a/config/app.php b/config/app.php
index 98eaee9..73bcaaf 100644
--- a/config/app.php
+++ b/config/app.php
@@ -152,4 +152,5 @@ return [
'version' => env('APP_VERSION', null),
+ 'catalog_url' => env('CATALOG_URL', 'https://raw.githubusercontent.com/bnussbau/trmnl-recipe-catalog/refs/heads/main/catalog.yaml'),
];
diff --git a/resources/views/livewire/catalog/index.blade.php b/resources/views/livewire/catalog/index.blade.php
new file mode 100644
index 0000000..4725e68
--- /dev/null
+++ b/resources/views/livewire/catalog/index.blade.php
@@ -0,0 +1,148 @@
+loadCatalogPlugins();
+ }
+
+ private function loadCatalogPlugins(): void
+ {
+ $catalogUrl = config('app.catalog_url');
+
+ $this->catalogPlugins = Cache::remember('catalog_plugins', 43200, function () use ($catalogUrl) {
+ try {
+ $response = Http::get($catalogUrl);
+ $catalogContent = $response->body();
+ $catalog = Yaml::parse($catalogContent);
+
+ return collect($catalog)->map(function ($plugin, $key) {
+ return [
+ 'id' => $key,
+ 'name' => $plugin['name'] ?? 'Unknown Plugin',
+ 'description' => $plugin['author_bio']['description'] ?? '',
+ 'author' => $plugin['author']['name'] ?? 'Unknown Author',
+ 'github' => $plugin['author']['github'] ?? null,
+ 'license' => $plugin['license'] ?? null,
+ 'zip_url' => $plugin['trmnlp']['zip_url'] ?? null,
+ 'repo_url' => $plugin['trmnlp']['repo'] ?? null,
+ 'logo_url' => $plugin['logo_url'] ?? null,
+ 'screenshot_url' => $plugin['screenshot_url'] ?? null,
+ 'learn_more_url' => $plugin['author_bio']['learn_more_url'] ?? null,
+ ];
+ })->toArray();
+ } catch (\Exception $e) {
+ Log::error('Failed to load catalog from URL: ' . $e->getMessage());
+ return [];
+ }
+ });
+ }
+
+ public function installPlugin(string $pluginId, PluginImportService $pluginImportService): void
+ {
+ abort_unless(auth()->user() !== null, 403);
+
+ $plugin = collect($this->catalogPlugins)->firstWhere('id', $pluginId);
+
+ if (!$plugin || !$plugin['zip_url']) {
+ $this->addError('installation', 'Plugin not found or no download URL available.');
+ return;
+ }
+
+ $this->installingPlugin = $pluginId;
+
+ try {
+ $importedPlugin = $pluginImportService->importFromUrl($plugin['zip_url'], auth()->user());
+
+ $this->dispatch('plugin-installed');
+ Flux::modal('import-from-catalog')->close();
+
+ } catch (\Exception $e) {
+ $this->addError('installation', 'Error installing plugin: ' . $e->getMessage());
+ } finally {
+ $this->installingPlugin = '';
+ }
+ }
+}; ?>
+
+
+ @if(empty($catalogPlugins))
+
+
+ No plugins available
+ Catalog is empty
+
+ @else
+
+ @error('installation')
+
+ @enderror
+
+ @foreach($catalogPlugins as $plugin)
+
+
+ @if($plugin['logo_url'])
+
![{{ $plugin['name'] }}]({{ $plugin['logo_url'] }})
+ @else
+
+
+
+ @endif
+
+
+
+
+
{{ $plugin['name'] }}
+ @if ($plugin['github'])
+
by {{ $plugin['github'] }}
+ @endif
+
+
+ @if($plugin['license'])
+
{{ $plugin['license'] }}
+ @endif
+ @if($plugin['repo_url'])
+
+
+
+ @endif
+
+
+
+ @if($plugin['description'])
+
{{ $plugin['description'] }}
+ @endif
+
+
+
+ Install
+
+
+ @if($plugin['learn_more_url'])
+
+ Learn More
+
+ @endif
+
+
+
+
+ @endforeach
+
+ @endif
+
diff --git a/resources/views/livewire/plugins/index.blade.php b/resources/views/livewire/plugins/index.blade.php
index 9a5dd69..828e051 100644
--- a/resources/views/livewire/plugins/index.blade.php
+++ b/resources/views/livewire/plugins/index.blade.php
@@ -36,7 +36,7 @@ new class extends Component {
'polling_body' => 'nullable|string',
];
- private function refreshPlugins(): void
+ public function refreshPlugins(): void
{
$userPlugins = auth()->user()?->plugins?->map(function ($plugin) {
return $plugin->toArray();
@@ -96,10 +96,8 @@ new class extends Component {
$this->reset(['zipFile']);
Flux::modal('import-zip')->close();
- $this->dispatch('notify', ['type' => 'success', 'message' => 'Plugin imported successfully!']);
-
} catch (\Exception $e) {
- $this->dispatch('notify', ['type' => 'error', 'message' => 'Error importing plugin: ' . $e->getMessage()]);
+ $this->addError('zipFile', 'Error installing plugin: ' . $e->getMessage());
}
}
@@ -120,7 +118,10 @@ new class extends Component {
- Import Recipe
+ Import Recipe Archive
+
+
+ Import from Catalog
Seed Example Recipes
@@ -167,7 +168,7 @@ new class extends Component {