mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-01-13 15:07:49 +00:00
fix: recipe with shared.liquid template only should pass validation
This commit is contained in:
parent
3f98a70ad9
commit
7d1e74183d
2 changed files with 166 additions and 46 deletions
|
|
@ -21,11 +21,12 @@ class PluginImportService
|
|||
* Validate YAML settings
|
||||
*
|
||||
* @param array $settings The parsed YAML settings
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function validateYAML(array $settings): void
|
||||
{
|
||||
if (!isset($settings['custom_fields']) || !is_array($settings['custom_fields'])) {
|
||||
if (! isset($settings['custom_fields']) || ! is_array($settings['custom_fields'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -43,6 +44,7 @@ class PluginImportService
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a plugin from a ZIP file
|
||||
*
|
||||
|
|
@ -73,12 +75,17 @@ class PluginImportService
|
|||
$zip->extractTo($tempDir);
|
||||
$zip->close();
|
||||
|
||||
// Find the required files (settings.yml and full.liquid/full.blade.php)
|
||||
// Find the required files (settings.yml and full.liquid/full.blade.php/shared.liquid/shared.blade.php)
|
||||
$filePaths = $this->findRequiredFiles($tempDir, $zipEntryPath);
|
||||
|
||||
// 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
|
||||
if (! $filePaths['settingsYamlPath']) {
|
||||
throw new Exception('Invalid ZIP structure. Required file settings.yml is missing.');
|
||||
}
|
||||
|
||||
// Validate that we have at least one template file
|
||||
if (! $filePaths['fullLiquidPath'] && ! $filePaths['sharedLiquidPath'] && ! $filePaths['sharedBladePath']) {
|
||||
throw new Exception('Invalid ZIP structure. At least one of the following files is required: full.liquid, full.blade.php, shared.liquid, or shared.blade.php.');
|
||||
}
|
||||
|
||||
// Parse settings.yml
|
||||
|
|
@ -86,21 +93,38 @@ class PluginImportService
|
|||
$settings = Yaml::parse($settingsYaml);
|
||||
$this->validateYAML($settings);
|
||||
|
||||
// Read full.liquid content
|
||||
$fullLiquid = File::get($filePaths['fullLiquidPath']);
|
||||
// Determine which template file to use and read its content
|
||||
$templatePath = null;
|
||||
$markupLanguage = 'blade';
|
||||
|
||||
// Prepend shared.liquid content if available
|
||||
if ($filePaths['fullLiquidPath']) {
|
||||
$templatePath = $filePaths['fullLiquidPath'];
|
||||
$fullLiquid = File::get($templatePath);
|
||||
|
||||
// Prepend shared.liquid or shared.blade.php content if available
|
||||
if ($filePaths['sharedLiquidPath'] && File::exists($filePaths['sharedLiquidPath'])) {
|
||||
$sharedLiquid = File::get($filePaths['sharedLiquidPath']);
|
||||
$fullLiquid = $sharedLiquid."\n".$fullLiquid;
|
||||
} elseif ($filePaths['sharedBladePath'] && File::exists($filePaths['sharedBladePath'])) {
|
||||
$sharedBlade = File::get($filePaths['sharedBladePath']);
|
||||
$fullLiquid = $sharedBlade."\n".$fullLiquid;
|
||||
}
|
||||
|
||||
// Check if the file ends with .liquid to set markup language
|
||||
$markupLanguage = 'blade';
|
||||
if (pathinfo((string) $filePaths['fullLiquidPath'], PATHINFO_EXTENSION) === 'liquid') {
|
||||
if (pathinfo((string) $templatePath, PATHINFO_EXTENSION) === 'liquid') {
|
||||
$markupLanguage = 'liquid';
|
||||
$fullLiquid = '<div class="view view--{{ size }}">'."\n".$fullLiquid."\n".'</div>';
|
||||
}
|
||||
} elseif ($filePaths['sharedLiquidPath']) {
|
||||
$templatePath = $filePaths['sharedLiquidPath'];
|
||||
$fullLiquid = File::get($templatePath);
|
||||
$markupLanguage = 'liquid';
|
||||
$fullLiquid = '<div class="view view--{{ size }}">'."\n".$fullLiquid."\n".'</div>';
|
||||
} elseif ($filePaths['sharedBladePath']) {
|
||||
$templatePath = $filePaths['sharedBladePath'];
|
||||
$fullLiquid = File::get($templatePath);
|
||||
$markupLanguage = 'blade';
|
||||
}
|
||||
|
||||
// Ensure custom_fields is properly formatted
|
||||
if (! isset($settings['custom_fields']) || ! is_array($settings['custom_fields'])) {
|
||||
|
|
@ -204,12 +228,17 @@ class PluginImportService
|
|||
$zip->extractTo($tempDir);
|
||||
$zip->close();
|
||||
|
||||
// Find the required files (settings.yml and full.liquid/full.blade.php)
|
||||
// Find the required files (settings.yml and full.liquid/full.blade.php/shared.liquid/shared.blade.php)
|
||||
$filePaths = $this->findRequiredFiles($tempDir, $zipEntryPath);
|
||||
|
||||
// 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.');
|
||||
if (! $filePaths['settingsYamlPath']) {
|
||||
throw new Exception('Invalid ZIP structure. Required file settings.yml is missing.');
|
||||
}
|
||||
|
||||
// Validate that we have at least one template file
|
||||
if (! $filePaths['fullLiquidPath'] && ! $filePaths['sharedLiquidPath'] && ! $filePaths['sharedBladePath']) {
|
||||
throw new Exception('Invalid ZIP structure. At least one of the following files is required: full.liquid, full.blade.php, shared.liquid, or shared.blade.php.');
|
||||
}
|
||||
|
||||
// Parse settings.yml
|
||||
|
|
@ -217,21 +246,38 @@ class PluginImportService
|
|||
$settings = Yaml::parse($settingsYaml);
|
||||
$this->validateYAML($settings);
|
||||
|
||||
// Read full.liquid content
|
||||
$fullLiquid = File::get($filePaths['fullLiquidPath']);
|
||||
// Determine which template file to use and read its content
|
||||
$templatePath = null;
|
||||
$markupLanguage = 'blade';
|
||||
|
||||
// Prepend shared.liquid content if available
|
||||
if ($filePaths['fullLiquidPath']) {
|
||||
$templatePath = $filePaths['fullLiquidPath'];
|
||||
$fullLiquid = File::get($templatePath);
|
||||
|
||||
// Prepend shared.liquid or shared.blade.php content if available
|
||||
if ($filePaths['sharedLiquidPath'] && File::exists($filePaths['sharedLiquidPath'])) {
|
||||
$sharedLiquid = File::get($filePaths['sharedLiquidPath']);
|
||||
$fullLiquid = $sharedLiquid."\n".$fullLiquid;
|
||||
} elseif ($filePaths['sharedBladePath'] && File::exists($filePaths['sharedBladePath'])) {
|
||||
$sharedBlade = File::get($filePaths['sharedBladePath']);
|
||||
$fullLiquid = $sharedBlade."\n".$fullLiquid;
|
||||
}
|
||||
|
||||
// Check if the file ends with .liquid to set markup language
|
||||
$markupLanguage = 'blade';
|
||||
if (pathinfo((string) $filePaths['fullLiquidPath'], PATHINFO_EXTENSION) === 'liquid') {
|
||||
if (pathinfo((string) $templatePath, PATHINFO_EXTENSION) === 'liquid') {
|
||||
$markupLanguage = 'liquid';
|
||||
$fullLiquid = '<div class="view view--{{ size }}">'."\n".$fullLiquid."\n".'</div>';
|
||||
}
|
||||
} elseif ($filePaths['sharedLiquidPath']) {
|
||||
$templatePath = $filePaths['sharedLiquidPath'];
|
||||
$fullLiquid = File::get($templatePath);
|
||||
$markupLanguage = 'liquid';
|
||||
$fullLiquid = '<div class="view view--{{ size }}">'."\n".$fullLiquid."\n".'</div>';
|
||||
} elseif ($filePaths['sharedBladePath']) {
|
||||
$templatePath = $filePaths['sharedBladePath'];
|
||||
$fullLiquid = File::get($templatePath);
|
||||
$markupLanguage = 'blade';
|
||||
}
|
||||
|
||||
// Ensure custom_fields is properly formatted
|
||||
if (! isset($settings['custom_fields']) || ! is_array($settings['custom_fields'])) {
|
||||
|
|
@ -310,6 +356,7 @@ class PluginImportService
|
|||
$settingsYamlPath = null;
|
||||
$fullLiquidPath = null;
|
||||
$sharedLiquidPath = null;
|
||||
$sharedBladePath = null;
|
||||
|
||||
// If zipEntryPath is specified, look for files in that specific directory first
|
||||
if ($zipEntryPath) {
|
||||
|
|
@ -327,6 +374,8 @@ class PluginImportService
|
|||
|
||||
if (File::exists($targetDir.'/shared.liquid')) {
|
||||
$sharedLiquidPath = $targetDir.'/shared.liquid';
|
||||
} elseif (File::exists($targetDir.'/shared.blade.php')) {
|
||||
$sharedBladePath = $targetDir.'/shared.blade.php';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -342,15 +391,18 @@ class PluginImportService
|
|||
|
||||
if (File::exists($targetDir.'/src/shared.liquid')) {
|
||||
$sharedLiquidPath = $targetDir.'/src/shared.liquid';
|
||||
} elseif (File::exists($targetDir.'/src/shared.blade.php')) {
|
||||
$sharedBladePath = $targetDir.'/src/shared.blade.php';
|
||||
}
|
||||
}
|
||||
|
||||
// If we found the required files in the target directory, return them
|
||||
if ($settingsYamlPath && $fullLiquidPath) {
|
||||
if ($settingsYamlPath && ($fullLiquidPath || $sharedLiquidPath || $sharedBladePath)) {
|
||||
return [
|
||||
'settingsYamlPath' => $settingsYamlPath,
|
||||
'fullLiquidPath' => $fullLiquidPath,
|
||||
'sharedLiquidPath' => $sharedLiquidPath,
|
||||
'sharedBladePath' => $sharedBladePath,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -367,9 +419,11 @@ class PluginImportService
|
|||
$fullLiquidPath = $tempDir.'/src/full.blade.php';
|
||||
}
|
||||
|
||||
// Check for shared.liquid in the same directory
|
||||
// Check for shared.liquid or shared.blade.php in the same directory
|
||||
if (File::exists($tempDir.'/src/shared.liquid')) {
|
||||
$sharedLiquidPath = $tempDir.'/src/shared.liquid';
|
||||
} elseif (File::exists($tempDir.'/src/shared.blade.php')) {
|
||||
$sharedBladePath = $tempDir.'/src/shared.blade.php';
|
||||
}
|
||||
} else {
|
||||
// Search for the files in the extracted directory structure
|
||||
|
|
@ -386,20 +440,24 @@ class PluginImportService
|
|||
$fullLiquidPath = $filepath;
|
||||
} elseif ($filename === 'shared.liquid') {
|
||||
$sharedLiquidPath = $filepath;
|
||||
} elseif ($filename === 'shared.blade.php') {
|
||||
$sharedBladePath = $filepath;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if shared.liquid exists in the same directory as full.liquid
|
||||
if ($settingsYamlPath && $fullLiquidPath && ! $sharedLiquidPath) {
|
||||
// Check if shared.liquid or shared.blade.php exists in the same directory as full.liquid
|
||||
if ($settingsYamlPath && $fullLiquidPath && ! $sharedLiquidPath && ! $sharedBladePath) {
|
||||
$fullLiquidDir = dirname((string) $fullLiquidPath);
|
||||
if (File::exists($fullLiquidDir.'/shared.liquid')) {
|
||||
$sharedLiquidPath = $fullLiquidDir.'/shared.liquid';
|
||||
} elseif (File::exists($fullLiquidDir.'/shared.blade.php')) {
|
||||
$sharedBladePath = $fullLiquidDir.'/shared.blade.php';
|
||||
}
|
||||
}
|
||||
|
||||
// If we found the files but they're not in the src folder,
|
||||
// check if they're in the root of the ZIP or in a subfolder
|
||||
if ($settingsYamlPath && $fullLiquidPath) {
|
||||
if ($settingsYamlPath && ($fullLiquidPath || $sharedLiquidPath || $sharedBladePath)) {
|
||||
// If the files are in the root of the ZIP, create a src folder and move them there
|
||||
$srcDir = dirname((string) $settingsYamlPath);
|
||||
|
||||
|
|
@ -410,17 +468,25 @@ class PluginImportService
|
|||
|
||||
// Copy the files to the src directory
|
||||
File::copy($settingsYamlPath, $newSrcDir.'/settings.yml');
|
||||
File::copy($fullLiquidPath, $newSrcDir.'/full.liquid');
|
||||
|
||||
// Copy shared.liquid if it exists
|
||||
// Copy full.liquid or full.blade.php if it exists
|
||||
if ($fullLiquidPath) {
|
||||
$extension = pathinfo((string) $fullLiquidPath, PATHINFO_EXTENSION);
|
||||
File::copy($fullLiquidPath, $newSrcDir.'/full.'.$extension);
|
||||
$fullLiquidPath = $newSrcDir.'/full.'.$extension;
|
||||
}
|
||||
|
||||
// Copy shared.liquid or shared.blade.php if it exists
|
||||
if ($sharedLiquidPath) {
|
||||
File::copy($sharedLiquidPath, $newSrcDir.'/shared.liquid');
|
||||
$sharedLiquidPath = $newSrcDir.'/shared.liquid';
|
||||
} elseif ($sharedBladePath) {
|
||||
File::copy($sharedBladePath, $newSrcDir.'/shared.blade.php');
|
||||
$sharedBladePath = $newSrcDir.'/shared.blade.php';
|
||||
}
|
||||
|
||||
// Update the paths
|
||||
$settingsYamlPath = $newSrcDir.'/settings.yml';
|
||||
$fullLiquidPath = $newSrcDir.'/full.liquid';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -429,6 +495,7 @@ class PluginImportService
|
|||
'settingsYamlPath' => $settingsYamlPath,
|
||||
'fullLiquidPath' => $fullLiquidPath,
|
||||
'sharedLiquidPath' => $sharedLiquidPath,
|
||||
'sharedBladePath' => $sharedBladePath,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,19 +83,34 @@ it('throws exception for invalid zip file', function (): void {
|
|||
->toThrow(Exception::class, 'Could not open the ZIP file.');
|
||||
});
|
||||
|
||||
it('throws exception for missing required files', function (): void {
|
||||
it('throws exception for missing settings.yml', function (): void {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$zipContent = createMockZipFile([
|
||||
'src/settings.yml' => getValidSettingsYaml(),
|
||||
// Missing full.liquid
|
||||
'src/full.liquid' => getValidFullLiquid(),
|
||||
// Missing settings.yml
|
||||
]);
|
||||
|
||||
$zipFile = UploadedFile::fake()->createWithContent('test-plugin.zip', $zipContent);
|
||||
|
||||
$pluginImportService = new PluginImportService();
|
||||
expect(fn (): Plugin => $pluginImportService->importFromZip($zipFile, $user))
|
||||
->toThrow(Exception::class, 'Invalid ZIP structure. Required files settings.yml and full.liquid are missing.');
|
||||
->toThrow(Exception::class, 'Invalid ZIP structure. Required file settings.yml is missing.');
|
||||
});
|
||||
|
||||
it('throws exception for missing template files', function (): void {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$zipContent = createMockZipFile([
|
||||
'src/settings.yml' => getValidSettingsYaml(),
|
||||
// Missing all template files
|
||||
]);
|
||||
|
||||
$zipFile = UploadedFile::fake()->createWithContent('test-plugin.zip', $zipContent);
|
||||
|
||||
$pluginImportService = new PluginImportService();
|
||||
expect(fn (): Plugin => $pluginImportService->importFromZip($zipFile, $user))
|
||||
->toThrow(Exception::class, 'Invalid ZIP structure. At least one of the following files is required: full.liquid, full.blade.php, shared.liquid, or shared.blade.php.');
|
||||
});
|
||||
|
||||
it('sets default values when settings are missing', function (): void {
|
||||
|
|
@ -431,7 +446,7 @@ it('throws exception when multi_string default value contains a comma', function
|
|||
$user = User::factory()->create();
|
||||
|
||||
// YAML with a comma in the 'default' field of a multi_string
|
||||
$invalidYaml = <<<YAML
|
||||
$invalidYaml = <<<'YAML'
|
||||
name: Test Plugin
|
||||
refresh_interval: 30
|
||||
strategy: static
|
||||
|
|
@ -453,14 +468,14 @@ YAML;
|
|||
$pluginImportService = new PluginImportService();
|
||||
|
||||
expect(fn () => $pluginImportService->importFromZip($zipFile, $user))
|
||||
->toThrow(Exception::class, "Validation Error: The default value for multistring fields like `api_key` cannot contain commas.");
|
||||
->toThrow(Exception::class, 'Validation Error: The default value for multistring fields like `api_key` cannot contain commas.');
|
||||
});
|
||||
|
||||
it('throws exception when multi_string placeholder contains a comma', function (): void {
|
||||
$user = User::factory()->create();
|
||||
|
||||
// YAML with a comma in the 'placeholder' field
|
||||
$invalidYaml = <<<YAML
|
||||
$invalidYaml = <<<'YAML'
|
||||
name: Test Plugin
|
||||
refresh_interval: 30
|
||||
strategy: static
|
||||
|
|
@ -483,7 +498,45 @@ YAML;
|
|||
$pluginImportService = new PluginImportService();
|
||||
|
||||
expect(fn () => $pluginImportService->importFromZip($zipFile, $user))
|
||||
->toThrow(Exception::class, "Validation Error: The placeholder value for multistring fields like `api_key` cannot contain commas.");
|
||||
->toThrow(Exception::class, 'Validation Error: The placeholder value for multistring fields like `api_key` cannot contain commas.');
|
||||
});
|
||||
|
||||
it('imports plugin with only shared.liquid file', function (): void {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$zipContent = createMockZipFile([
|
||||
'src/settings.yml' => getValidSettingsYaml(),
|
||||
'src/shared.liquid' => '<div class="shared-content">{{ data.title }}</div>',
|
||||
]);
|
||||
|
||||
$zipFile = UploadedFile::fake()->createWithContent('test-plugin.zip', $zipContent);
|
||||
|
||||
$pluginImportService = new PluginImportService();
|
||||
$plugin = $pluginImportService->importFromZip($zipFile, $user);
|
||||
|
||||
expect($plugin)->toBeInstanceOf(Plugin::class)
|
||||
->and($plugin->markup_language)->toBe('liquid')
|
||||
->and($plugin->render_markup)->toContain('<div class="view view--{{ size }}">')
|
||||
->and($plugin->render_markup)->toContain('<div class="shared-content">{{ data.title }}</div>');
|
||||
});
|
||||
|
||||
it('imports plugin with only shared.blade.php file', function (): void {
|
||||
$user = User::factory()->create();
|
||||
|
||||
$zipContent = createMockZipFile([
|
||||
'src/settings.yml' => getValidSettingsYaml(),
|
||||
'src/shared.blade.php' => '<div class="shared-content">{{ $data["title"] }}</div>',
|
||||
]);
|
||||
|
||||
$zipFile = UploadedFile::fake()->createWithContent('test-plugin.zip', $zipContent);
|
||||
|
||||
$pluginImportService = new PluginImportService();
|
||||
$plugin = $pluginImportService->importFromZip($zipFile, $user);
|
||||
|
||||
expect($plugin)->toBeInstanceOf(Plugin::class)
|
||||
->and($plugin->markup_language)->toBe('blade')
|
||||
->and($plugin->render_markup)->toBe('<div class="shared-content">{{ $data["title"] }}</div>')
|
||||
->and($plugin->render_markup)->not->toContain('<div class="view view--{{ size }}">');
|
||||
});
|
||||
|
||||
// Helper methods
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue