diff --git a/app/Models/PlaylistItem.php b/app/Models/PlaylistItem.php index 28f6454..3040e39 100644 --- a/app/Models/PlaylistItem.php +++ b/app/Models/PlaylistItem.php @@ -140,7 +140,7 @@ class PlaylistItem extends Model if (! $this->isMashup()) { return view('trmnl-layouts.single', [ 'colorDepth' => $device?->deviceModel?->color_depth, - 'deviceVariant' => $device?->deviceModel->name ?? 'og', + 'deviceVariant' => $device?->deviceModel?->name ?? 'og', 'scaleLevel' => $device?->deviceModel?->scale_level, 'slot' => $this->plugin instanceof Plugin ? $this->plugin->render('full', false) @@ -164,7 +164,7 @@ class PlaylistItem extends Model return view('trmnl-layouts.mashup', [ 'colorDepth' => $device?->deviceModel?->color_depth, - 'deviceVariant' => $device?->deviceModel->name ?? 'og', + 'deviceVariant' => $device?->deviceModel?->name ?? 'og', 'scaleLevel' => $device?->deviceModel?->scale_level, 'mashupLayout' => $this->getMashupLayoutType(), 'slot' => implode('', $pluginMarkups), diff --git a/app/Models/Plugin.php b/app/Models/Plugin.php index 7c6d2c1..8f0ec75 100644 --- a/app/Models/Plugin.php +++ b/app/Models/Plugin.php @@ -346,7 +346,7 @@ class Plugin extends Model if ($size === 'full') { return view('trmnl-layouts.single', [ 'colorDepth' => $device?->deviceModel?->color_depth, - 'deviceVariant' => $device?->deviceModel->name ?? 'og', + 'deviceVariant' => $device?->deviceModel?->name ?? 'og', 'scaleLevel' => $device?->deviceModel?->scale_level, 'slot' => $renderedContent, ])->render(); @@ -354,7 +354,7 @@ class Plugin extends Model return view('trmnl-layouts.mashup', [ 'mashupLayout' => $this->getPreviewMashupLayoutForSize($size), 'colorDepth' => $device?->deviceModel?->color_depth, - 'deviceVariant' => $device?->deviceModel->name ?? 'og', + 'deviceVariant' => $device?->deviceModel?->name ?? 'og', 'scaleLevel' => $device?->deviceModel?->scale_level, 'slot' => $renderedContent, ])->render(); @@ -368,7 +368,7 @@ class Plugin extends Model if ($standalone) { return view('trmnl-layouts.single', [ 'colorDepth' => $device?->deviceModel?->color_depth, - 'deviceVariant' => $device?->deviceModel->name ?? 'og', + 'deviceVariant' => $device?->deviceModel?->name ?? 'og', 'scaleLevel' => $device?->deviceModel?->scale_level, 'slot' => view($this->render_markup_view, [ 'size' => $size, diff --git a/app/Services/PluginImportService.php b/app/Services/PluginImportService.php index c409d99..29b5688 100644 --- a/app/Services/PluginImportService.php +++ b/app/Services/PluginImportService.php @@ -22,12 +22,11 @@ class PluginImportService * * @param UploadedFile $zipFile The uploaded ZIP file * @param User $user The user importing the plugin - * @param string|null $zipEntryPath Optional path to specific plugin in monorepo * @return Plugin The created plugin instance * * @throws Exception If the ZIP file is invalid or required files are missing */ - public function importFromZip(UploadedFile $zipFile, User $user, ?string $zipEntryPath = null): Plugin + public function importFromZip(UploadedFile $zipFile, User $user): Plugin { // Create a temporary directory using Laravel's temporary directory helper $tempDirName = 'temp/'.uniqid('plugin_import_', true); @@ -48,7 +47,7 @@ class PluginImportService $zip->close(); // Find the required files (settings.yml and full.liquid/full.blade.php) - $filePaths = $this->findRequiredFiles($tempDir, $zipEntryPath); + $filePaths = $this->findRequiredFiles($tempDir); // Validate that we found the required files if (! $filePaths['settingsYamlPath'] || ! $filePaths['fullLiquidPath']) { @@ -139,12 +138,11 @@ class PluginImportService * * @param string $zipUrl The URL to the ZIP file * @param User $user The user importing the plugin - * @param string|null $zipEntryPath Optional path to specific plugin in monorepo * @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, ?string $zipEntryPath = null): Plugin + public function importFromUrl(string $zipUrl, User $user): Plugin { // Download the ZIP file $response = Http::timeout(60)->get($zipUrl); @@ -173,7 +171,7 @@ class PluginImportService $zip->close(); // Find the required files (settings.yml and full.liquid/full.blade.php) - $filePaths = $this->findRequiredFiles($tempDir, $zipEntryPath); + $filePaths = $this->findRequiredFiles($tempDir); // Validate that we found the required files if (! $filePaths['settingsYamlPath'] || ! $filePaths['fullLiquidPath']) { @@ -259,57 +257,12 @@ class PluginImportService } } - private function findRequiredFiles(string $tempDir, ?string $zipEntryPath = null): array + private function findRequiredFiles(string $tempDir): array { $settingsYamlPath = null; $fullLiquidPath = null; $sharedLiquidPath = null; - // If zipEntryPath is specified, look for files in that specific directory first - if ($zipEntryPath) { - $targetDir = $tempDir . '/' . $zipEntryPath; - if (File::exists($targetDir)) { - // Check if files are directly in the target directory - if (File::exists($targetDir . '/settings.yml')) { - $settingsYamlPath = $targetDir . '/settings.yml'; - - if (File::exists($targetDir . '/full.liquid')) { - $fullLiquidPath = $targetDir . '/full.liquid'; - } elseif (File::exists($targetDir . '/full.blade.php')) { - $fullLiquidPath = $targetDir . '/full.blade.php'; - } - - if (File::exists($targetDir . '/shared.liquid')) { - $sharedLiquidPath = $targetDir . '/shared.liquid'; - } - } - - // Check if files are in src subdirectory of target directory - if (!$settingsYamlPath && File::exists($targetDir . '/src/settings.yml')) { - $settingsYamlPath = $targetDir . '/src/settings.yml'; - - if (File::exists($targetDir . '/src/full.liquid')) { - $fullLiquidPath = $targetDir . '/src/full.liquid'; - } elseif (File::exists($targetDir . '/src/full.blade.php')) { - $fullLiquidPath = $targetDir . '/src/full.blade.php'; - } - - if (File::exists($targetDir . '/src/shared.liquid')) { - $sharedLiquidPath = $targetDir . '/src/shared.liquid'; - } - } - - // If we found the required files in the target directory, return them - if ($settingsYamlPath && $fullLiquidPath) { - return [ - 'settingsYamlPath' => $settingsYamlPath, - 'fullLiquidPath' => $fullLiquidPath, - 'sharedLiquidPath' => $sharedLiquidPath, - ]; - } - } - } - // First, check if files are directly in the src folder if (File::exists($tempDir.'/src/settings.yml')) { $settingsYamlPath = $tempDir.'/src/settings.yml'; diff --git a/resources/views/livewire/catalog/index.blade.php b/resources/views/livewire/catalog/index.blade.php index 5bdae10..92bd5a9 100644 --- a/resources/views/livewire/catalog/index.blade.php +++ b/resources/views/livewire/catalog/index.blade.php @@ -53,7 +53,6 @@ new class extends Component { 'github' => Arr::get($plugin, 'author.github'), 'license' => Arr::get($plugin, 'license'), 'zip_url' => Arr::get($plugin, 'trmnlp.zip_url'), - 'zip_entry_path' => Arr::get($plugin, 'trmnlp.zip_entry_path'), 'repo_url' => Arr::get($plugin, 'trmnlp.repo'), 'logo_url' => Arr::get($plugin, 'logo_url'), 'screenshot_url' => Arr::get($plugin, 'screenshot_url'), @@ -83,7 +82,7 @@ new class extends Component { $this->installingPlugin = $pluginId; try { - $importedPlugin = $pluginImportService->importFromUrl($plugin['zip_url'], auth()->user(), $plugin['zip_entry_path'] ?? null); + $importedPlugin = $pluginImportService->importFromUrl($plugin['zip_url'], auth()->user()); $this->dispatch('plugin-installed'); Flux::modal('import-from-catalog')->close(); diff --git a/tests/Feature/PluginImportTest.php b/tests/Feature/PluginImportTest.php index 86b9220..25325d2 100644 --- a/tests/Feature/PluginImportTest.php +++ b/tests/Feature/PluginImportTest.php @@ -6,7 +6,6 @@ use App\Models\Plugin; use App\Models\User; use App\Services\PluginImportService; use Illuminate\Http\UploadedFile; -use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Storage; beforeEach(function () { @@ -133,221 +132,11 @@ it('handles blade markup language correctly', function () { expect($plugin->markup_language)->toBe('blade'); }); -it('imports plugin from monorepo with zip_entry_path parameter', function () { - $user = User::factory()->create(); - - // Create a mock ZIP file with plugin in a subdirectory - $zipContent = createMockZipFile([ - 'example-plugin/settings.yml' => getValidSettingsYaml(), - 'example-plugin/full.liquid' => getValidFullLiquid(), - ]); - - $zipFile = UploadedFile::fake()->createWithContent('monorepo.zip', $zipContent); - - $pluginImportService = new PluginImportService(); - $plugin = $pluginImportService->importFromZip($zipFile, $user); - - expect($plugin)->toBeInstanceOf(Plugin::class) - ->and($plugin->user_id)->toBe($user->id) - ->and($plugin->name)->toBe('Test Plugin'); -}); - -it('imports plugin from monorepo with src subdirectory', function () { - $user = User::factory()->create(); - - // Create a mock ZIP file with plugin in a subdirectory with src folder - $zipContent = createMockZipFile([ - 'example-plugin/src/settings.yml' => getValidSettingsYaml(), - 'example-plugin/src/full.liquid' => getValidFullLiquid(), - ]); - - $zipFile = UploadedFile::fake()->createWithContent('monorepo.zip', $zipContent); - - $pluginImportService = new PluginImportService(); - $plugin = $pluginImportService->importFromZip($zipFile, $user); - - expect($plugin)->toBeInstanceOf(Plugin::class) - ->and($plugin->user_id)->toBe($user->id) - ->and($plugin->name)->toBe('Test Plugin'); -}); - -it('imports plugin from monorepo with shared.liquid in subdirectory', function () { - $user = User::factory()->create(); - - $zipContent = createMockZipFile([ - 'example-plugin/settings.yml' => getValidSettingsYaml(), - 'example-plugin/full.liquid' => getValidFullLiquid(), - 'example-plugin/shared.liquid' => '{% comment %}Monorepo shared styles{% endcomment %}', - ]); - - $zipFile = UploadedFile::fake()->createWithContent('monorepo.zip', $zipContent); - - $pluginImportService = new PluginImportService(); - $plugin = $pluginImportService->importFromZip($zipFile, $user); - - expect($plugin->render_markup)->toContain('{% comment %}Monorepo shared styles{% endcomment %}') - ->and($plugin->render_markup)->toContain('
'); -}); - -it('imports plugin from URL with zip_entry_path parameter', function () { - $user = User::factory()->create(); - - // Create a mock ZIP file with plugin in a subdirectory - $zipContent = createMockZipFile([ - 'example-plugin/settings.yml' => getValidSettingsYaml(), - 'example-plugin/full.liquid' => getValidFullLiquid(), - ]); - - // Mock the HTTP response - Http::fake([ - 'https://github.com/example/repo/archive/refs/heads/main.zip' => Http::response($zipContent, 200), - ]); - - $pluginImportService = new PluginImportService(); - $plugin = $pluginImportService->importFromUrl( - 'https://github.com/example/repo/archive/refs/heads/main.zip', - $user, - 'example-plugin' - ); - - expect($plugin)->toBeInstanceOf(Plugin::class) - ->and($plugin->user_id)->toBe($user->id) - ->and($plugin->name)->toBe('Test Plugin'); - - Http::assertSent(function ($request) { - return $request->url() === 'https://github.com/example/repo/archive/refs/heads/main.zip'; - }); -}); - -it('imports plugin from URL with zip_entry_path and src subdirectory', function () { - $user = User::factory()->create(); - - // Create a mock ZIP file with plugin in a subdirectory with src folder - $zipContent = createMockZipFile([ - 'example-plugin/src/settings.yml' => getValidSettingsYaml(), - 'example-plugin/src/full.liquid' => getValidFullLiquid(), - ]); - - // Mock the HTTP response - Http::fake([ - 'https://github.com/example/repo/archive/refs/heads/main.zip' => Http::response($zipContent, 200), - ]); - - $pluginImportService = new PluginImportService(); - $plugin = $pluginImportService->importFromUrl( - 'https://github.com/example/repo/archive/refs/heads/main.zip', - $user, - 'example-plugin' - ); - - expect($plugin)->toBeInstanceOf(Plugin::class) - ->and($plugin->user_id)->toBe($user->id) - ->and($plugin->name)->toBe('Test Plugin'); -}); - -it('imports plugin from GitHub monorepo with repository-named directory', function () { - $user = User::factory()->create(); - - // Create a mock ZIP file that simulates GitHub's ZIP structure with repository-named directory - $zipContent = createMockZipFile([ - 'example-repo-main/another-plugin/src/settings.yml' => "name: Other Plugin\nrefresh_interval: 60\nstrategy: static\npolling_verb: get\nstatic_data: '{}'\ncustom_fields: []", - 'example-repo-main/another-plugin/src/full.liquid' => '
Other content
', - 'example-repo-main/example-plugin/src/settings.yml' => getValidSettingsYaml(), - 'example-repo-main/example-plugin/src/full.liquid' => getValidFullLiquid(), - ]); - - // Mock the HTTP response - Http::fake([ - 'https://github.com/example/repo/archive/refs/heads/main.zip' => Http::response($zipContent, 200), - ]); - - $pluginImportService = new PluginImportService(); - $plugin = $pluginImportService->importFromUrl( - 'https://github.com/example/repo/archive/refs/heads/main.zip', - $user, - 'example-repo-main/example-plugin' - ); - - expect($plugin)->toBeInstanceOf(Plugin::class) - ->and($plugin->user_id)->toBe($user->id) - ->and($plugin->name)->toBe('Test Plugin'); // Should be from example-plugin, not other-plugin -}); - -it('finds required files in simple ZIP structure', function () { - $user = User::factory()->create(); - - // Create a simple ZIP file with just one plugin - $zipContent = createMockZipFile([ - 'example-repo-main/example-plugin/src/settings.yml' => getValidSettingsYaml(), - 'example-repo-main/example-plugin/src/full.liquid' => getValidFullLiquid(), - ]); - - $zipFile = UploadedFile::fake()->createWithContent('simple.zip', $zipContent); - - $pluginImportService = new PluginImportService(); - $plugin = $pluginImportService->importFromZip($zipFile, $user); - - expect($plugin)->toBeInstanceOf(Plugin::class) - ->and($plugin->user_id)->toBe($user->id) - ->and($plugin->name)->toBe('Test Plugin'); -}); - -it('finds required files in GitHub monorepo structure with zip_entry_path', function () { - $user = User::factory()->create(); - - // Create a mock ZIP file that simulates GitHub's ZIP structure - $zipContent = createMockZipFile([ - 'example-repo-main/example-plugin/src/settings.yml' => getValidSettingsYaml(), - 'example-repo-main/example-plugin/src/full.liquid' => getValidFullLiquid(), - 'example-repo-main/other-plugin/src/settings.yml' => "name: Other Plugin\nrefresh_interval: 60\nstrategy: static\npolling_verb: get\nstatic_data: '{}'\ncustom_fields: []", - 'example-repo-main/other-plugin/src/full.liquid' => '
Other content
', - ]); - - $zipFile = UploadedFile::fake()->createWithContent('monorepo.zip', $zipContent); - - $pluginImportService = new PluginImportService(); - $plugin = $pluginImportService->importFromZip($zipFile, $user, 'example-repo-main/example-plugin'); - - expect($plugin)->toBeInstanceOf(Plugin::class) - ->and($plugin->user_id)->toBe($user->id) - ->and($plugin->name)->toBe('Test Plugin'); // Should be from example-plugin, not other-plugin -}); - -it('imports specific plugin from monorepo zip with zip_entry_path parameter', function () { - $user = User::factory()->create(); - - // Create a mock ZIP file with 2 plugins in a monorepo structure - $zipContent = createMockZipFile([ - 'example-plugin/settings.yml' => getValidSettingsYaml(), - 'example-plugin/full.liquid' => getValidFullLiquid(), - 'example-plugin/shared.liquid' => '{% comment %}Monorepo shared styles{% endcomment %}', - 'example-plugin2/settings.yml' => "name: Example Plugin 2\nrefresh_interval: 45\nstrategy: static\npolling_verb: get\nstatic_data: '{}'\ncustom_fields: []", - 'example-plugin2/full.liquid' => '
Plugin 2 content
', - 'example-plugin2/shared.liquid' => '{% comment %}Plugin 2 shared styles{% endcomment %}', - ]); - - $zipFile = UploadedFile::fake()->createWithContent('monorepo.zip', $zipContent); - - $pluginImportService = new PluginImportService(); - - // This test will fail because importFromZip doesn't support zip_entry_path parameter yet - // The logic needs to be implemented to specify which plugin to import from the monorepo - $plugin = $pluginImportService->importFromZip($zipFile, $user, 'example-plugin2'); - - expect($plugin)->toBeInstanceOf(Plugin::class) - ->and($plugin->user_id)->toBe($user->id) - ->and($plugin->name)->toBe('Example Plugin 2') // Should import example-plugin2, not example-plugin - ->and($plugin->render_markup)->toContain('{% comment %}Plugin 2 shared styles{% endcomment %}') - ->and($plugin->render_markup)->toContain('
Plugin 2 content
'); -}); - // Helper methods function createMockZipFile(array $files): string { $zip = new ZipArchive(); - - $tempFileName = 'test_zip_'.uniqid().'.zip'; - $tempFile = Storage::path($tempFileName); + $tempFile = tempnam(sys_get_temp_dir(), 'test_zip_'); $zip->open($tempFile, ZipArchive::CREATE); @@ -358,8 +147,7 @@ function createMockZipFile(array $files): string $zip->close(); $content = file_get_contents($tempFile); - - Storage::delete($tempFileName); + unlink($tempFile); return $content; }