mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-01-14 15:37:53 +00:00
Compare commits
18 commits
2045ce9966
...
418f0cb59f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
418f0cb59f | ||
|
|
be05a20df3 | ||
|
|
e1db2a04e8 | ||
|
|
791815b77b | ||
|
|
cb39c7e8ca | ||
|
|
77bf4f1c96 | ||
|
|
f0e5e6261a | ||
|
|
a077a76049 | ||
|
|
54a5aa649e | ||
|
|
e980928a96 | ||
|
|
ca9532b6e9 | ||
|
|
964d015087 | ||
|
|
cf7ea6cd3c | ||
|
|
aa80e944f9 | ||
|
|
a7a541da42 | ||
|
|
73eabe8262 | ||
|
|
0e9a74965b | ||
|
|
66f876976b |
5 changed files with 12 additions and 282 deletions
|
|
@ -18,8 +18,6 @@ ENV TRMNL_LIQUID_ENABLED=1
|
||||||
# Switch to the root user so we can do root things
|
# Switch to the root user so we can do root things
|
||||||
USER root
|
USER root
|
||||||
|
|
||||||
COPY --chown=www-data:www-data --from=bnussbau/trmnl-liquid-cli:0.1.0 /usr/local/bin/trmnl-liquid-cli /usr/local/bin/
|
|
||||||
|
|
||||||
# Set the working directory
|
# Set the working directory
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
|
@ -53,5 +51,7 @@ FROM base AS production
|
||||||
# Copy the assets from the assets image
|
# Copy the assets from the assets image
|
||||||
COPY --chown=www-data:www-data --from=assets /app/public/build /var/www/html/public/build
|
COPY --chown=www-data:www-data --from=assets /app/public/build /var/www/html/public/build
|
||||||
COPY --chown=www-data:www-data --from=assets /app/node_modules /var/www/html/node_modules
|
COPY --chown=www-data:www-data --from=assets /app/node_modules /var/www/html/node_modules
|
||||||
|
|
||||||
|
COPY --chown=www-data:www-data --from=bnussbau/trmnl-liquid-cli:latest /usr/local/bin/trmnl-liquid-cli /usr/local/bin/
|
||||||
# Drop back to the www-data user
|
# Drop back to the www-data user
|
||||||
USER www-data
|
USER www-data
|
||||||
|
|
|
||||||
|
|
@ -130,10 +130,9 @@ class Plugin extends Model
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve Liquid variables in the entire polling_url field first, then split by newline
|
// Split URLs by newline and filter out empty lines
|
||||||
$resolvedPollingUrls = $this->resolveLiquidVariables($this->polling_url);
|
|
||||||
$urls = array_filter(
|
$urls = array_filter(
|
||||||
array_map('trim', explode("\n", $resolvedPollingUrls)),
|
array_map('trim', explode("\n", $this->polling_url)),
|
||||||
fn ($url): bool => ! empty($url)
|
fn ($url): bool => ! empty($url)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -148,8 +147,8 @@ class Plugin extends Model
|
||||||
$httpRequest = $httpRequest->withBody($resolvedBody);
|
$httpRequest = $httpRequest->withBody($resolvedBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL is already resolved, use it directly
|
// Resolve Liquid variables in the polling URL
|
||||||
$resolvedUrl = $url;
|
$resolvedUrl = $this->resolveLiquidVariables($url);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Make the request based on the verb
|
// Make the request based on the verb
|
||||||
|
|
@ -184,8 +183,8 @@ class Plugin extends Model
|
||||||
$httpRequest = $httpRequest->withBody($resolvedBody);
|
$httpRequest = $httpRequest->withBody($resolvedBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL is already resolved, use it directly
|
// Resolve Liquid variables in the polling URL
|
||||||
$resolvedUrl = $url;
|
$resolvedUrl = $this->resolveLiquidVariables($url);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Make the request based on the verb
|
// Make the request based on the verb
|
||||||
|
|
@ -242,7 +241,7 @@ class Plugin extends Model
|
||||||
try {
|
try {
|
||||||
// Attempt to parse it into JSON
|
// Attempt to parse it into JSON
|
||||||
$json = $httpResponse->json();
|
$json = $httpResponse->json();
|
||||||
if ($json !== null) {
|
if($json !== null) {
|
||||||
return $json;
|
return $json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -345,48 +344,19 @@ class Plugin extends Model
|
||||||
return $template;
|
return $template;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a template contains a Liquid for loop pattern
|
|
||||||
*
|
|
||||||
* @param string $template The template string to check
|
|
||||||
* @return bool True if the template contains a for loop pattern
|
|
||||||
*/
|
|
||||||
private function containsLiquidForLoop(string $template): bool
|
|
||||||
{
|
|
||||||
return preg_match('/{%-?\s*for\s+/i', $template) === 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve Liquid variables in a template string using the Liquid template engine
|
* Resolve Liquid variables in a template string using the Liquid template engine
|
||||||
*
|
*
|
||||||
* Uses the external trmnl-liquid renderer when:
|
|
||||||
* - preferred_renderer is 'trmnl-liquid'
|
|
||||||
* - External renderer is enabled in config
|
|
||||||
* - Template contains a Liquid for loop pattern
|
|
||||||
*
|
|
||||||
* Otherwise uses the internal PHP-based Liquid renderer.
|
|
||||||
*
|
|
||||||
* @param string $template The template string containing Liquid variables
|
* @param string $template The template string containing Liquid variables
|
||||||
* @return string The resolved template with variables replaced with their values
|
* @return string The resolved template with variables replaced with their values
|
||||||
*
|
*
|
||||||
* @throws LiquidException
|
* @throws LiquidException
|
||||||
* @throws Exception
|
|
||||||
*/
|
*/
|
||||||
public function resolveLiquidVariables(string $template): string
|
public function resolveLiquidVariables(string $template): string
|
||||||
{
|
{
|
||||||
// Get configuration variables - make them available at root level
|
// Get configuration variables - make them available at root level
|
||||||
$variables = $this->configuration ?? [];
|
$variables = $this->configuration ?? [];
|
||||||
|
|
||||||
// Check if external renderer should be used
|
|
||||||
$useExternalRenderer = $this->preferred_renderer === 'trmnl-liquid'
|
|
||||||
&& config('services.trmnl.liquid_enabled')
|
|
||||||
&& $this->containsLiquidForLoop($template);
|
|
||||||
|
|
||||||
if ($useExternalRenderer) {
|
|
||||||
// Use external Ruby liquid renderer
|
|
||||||
return $this->renderWithExternalLiquidRenderer($template, $variables);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the Liquid template engine to resolve variables
|
// Use the Liquid template engine to resolve variables
|
||||||
$environment = App::make('liquid.environment');
|
$environment = App::make('liquid.environment');
|
||||||
$environment->filterRegistry->register(StandardFilters::class);
|
$environment->filterRegistry->register(StandardFilters::class);
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ return [
|
||||||
'force_https' => env('FORCE_HTTPS', false),
|
'force_https' => env('FORCE_HTTPS', false),
|
||||||
'puppeteer_docker' => env('PUPPETEER_DOCKER', false),
|
'puppeteer_docker' => env('PUPPETEER_DOCKER', false),
|
||||||
'puppeteer_mode' => env('PUPPETEER_MODE', 'local'),
|
'puppeteer_mode' => env('PUPPETEER_MODE', 'local'),
|
||||||
'puppeteer_wait_for_network_idle' => env('PUPPETEER_WAIT_FOR_NETWORK_IDLE', true),
|
'puppeteer_wait_for_network_idle' => env('PUPPETEER_WAIT_FOR_NETWORK_IDLE', false),
|
||||||
'puppeteer_window_size_strategy' => env('PUPPETEER_WINDOW_SIZE_STRATEGY', null),
|
'puppeteer_window_size_strategy' => env('PUPPETEER_WINDOW_SIZE_STRATEGY', null),
|
||||||
|
|
||||||
'notifications' => [
|
'notifications' => [
|
||||||
|
|
|
||||||
|
|
@ -296,16 +296,6 @@ new class extends Component {
|
||||||
<flux:heading size="lg">Import from TRMNL Recipe Catalog
|
<flux:heading size="lg">Import from TRMNL Recipe Catalog
|
||||||
<flux:badge color="yellow" class="ml-2">Alpha</flux:badge>
|
<flux:badge color="yellow" class="ml-2">Alpha</flux:badge>
|
||||||
</flux:heading>
|
</flux:heading>
|
||||||
<flux:callout class="mb-4 mt-4" color="yellow">
|
|
||||||
<flux:heading size="sm">Limitations</flux:heading>
|
|
||||||
<ul class="list-disc pl-5 mt-2">
|
|
||||||
<li><flux:text>Only full view will be imported; shared markup will be prepended</flux:text></li>
|
|
||||||
<li><flux:text>Requires <span class="font-mono">trmnl-liquid-cli</span>. (Included in Docker container)</flux:text></li>
|
|
||||||
<li><flux:text>API responses in formats other than <span class="font-mono">JSON</span> are not yet fully supported.</flux:text></li>
|
|
||||||
<li><flux:text>There are limitations in payload size (Data Payload, Template).</flux:text></li>
|
|
||||||
</ul>
|
|
||||||
<flux:text class="mt-1">Please report issues, aside from the known limitations, on <a href="https://github.com/usetrmnl/byos_laravel/issues/new" target="_blank" class="underline">GitHub</a>. Include the recipe URL.</flux:text></li>
|
|
||||||
</flux:callout>
|
|
||||||
</div>
|
</div>
|
||||||
<livewire:catalog.trmnl lazy />
|
<livewire:catalog.trmnl lazy />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -357,233 +357,3 @@ test('resolveLiquidVariables handles empty configuration', function (): void {
|
||||||
|
|
||||||
expect($plugin->resolveLiquidVariables($template))->toBe($expected);
|
expect($plugin->resolveLiquidVariables($template))->toBe($expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('resolveLiquidVariables uses external renderer when preferred_renderer is trmnl-liquid and template contains for loop', function (): void {
|
|
||||||
Illuminate\Support\Facades\Process::fake([
|
|
||||||
'*' => Illuminate\Support\Facades\Process::result(
|
|
||||||
output: 'https://api1.example.com/data\nhttps://api2.example.com/data',
|
|
||||||
exitCode: 0
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
config(['services.trmnl.liquid_enabled' => true]);
|
|
||||||
config(['services.trmnl.liquid_path' => '/usr/local/bin/trmnl-liquid-cli']);
|
|
||||||
|
|
||||||
$plugin = Plugin::factory()->create([
|
|
||||||
'preferred_renderer' => 'trmnl-liquid',
|
|
||||||
'configuration' => [
|
|
||||||
'recipe_ids' => '1,2',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$template = <<<'LIQUID'
|
|
||||||
{% assign ids = recipe_ids | split: "," %}
|
|
||||||
{% for id in ids %}
|
|
||||||
https://api{{ id }}.example.com/data
|
|
||||||
{% endfor %}
|
|
||||||
LIQUID;
|
|
||||||
|
|
||||||
$result = $plugin->resolveLiquidVariables($template);
|
|
||||||
|
|
||||||
// Trim trailing newlines that may be added by the process
|
|
||||||
expect(mb_trim($result))->toBe('https://api1.example.com/data\nhttps://api2.example.com/data');
|
|
||||||
|
|
||||||
Illuminate\Support\Facades\Process::assertRan(function ($process): bool {
|
|
||||||
$command = is_array($process->command) ? implode(' ', $process->command) : $process->command;
|
|
||||||
|
|
||||||
return str_contains($command, 'trmnl-liquid-cli') &&
|
|
||||||
str_contains($command, '--template') &&
|
|
||||||
str_contains($command, '--context');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('resolveLiquidVariables uses internal renderer when preferred_renderer is not trmnl-liquid', function (): void {
|
|
||||||
$plugin = Plugin::factory()->create([
|
|
||||||
'preferred_renderer' => 'php',
|
|
||||||
'configuration' => [
|
|
||||||
'recipe_ids' => '1,2',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$template = <<<'LIQUID'
|
|
||||||
{% assign ids = recipe_ids | split: "," %}
|
|
||||||
{% for id in ids %}
|
|
||||||
https://api{{ id }}.example.com/data
|
|
||||||
{% endfor %}
|
|
||||||
LIQUID;
|
|
||||||
|
|
||||||
// Should use internal renderer even with for loop
|
|
||||||
$result = $plugin->resolveLiquidVariables($template);
|
|
||||||
|
|
||||||
// Internal renderer should process the template
|
|
||||||
expect($result)->toBeString();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('resolveLiquidVariables uses internal renderer when external renderer is disabled', function (): void {
|
|
||||||
config(['services.trmnl.liquid_enabled' => false]);
|
|
||||||
|
|
||||||
$plugin = Plugin::factory()->create([
|
|
||||||
'preferred_renderer' => 'trmnl-liquid',
|
|
||||||
'configuration' => [
|
|
||||||
'recipe_ids' => '1,2',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$template = <<<'LIQUID'
|
|
||||||
{% assign ids = recipe_ids | split: "," %}
|
|
||||||
{% for id in ids %}
|
|
||||||
https://api{{ id }}.example.com/data
|
|
||||||
{% endfor %}
|
|
||||||
LIQUID;
|
|
||||||
|
|
||||||
// Should use internal renderer when external is disabled
|
|
||||||
$result = $plugin->resolveLiquidVariables($template);
|
|
||||||
|
|
||||||
expect($result)->toBeString();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('resolveLiquidVariables uses internal renderer when template does not contain for loop', function (): void {
|
|
||||||
config(['services.trmnl.liquid_enabled' => true]);
|
|
||||||
config(['services.trmnl.liquid_path' => '/usr/local/bin/trmnl-liquid-cli']);
|
|
||||||
|
|
||||||
$plugin = Plugin::factory()->create([
|
|
||||||
'preferred_renderer' => 'trmnl-liquid',
|
|
||||||
'configuration' => [
|
|
||||||
'api_key' => 'test123',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$template = 'https://api.example.com/data?key={{ api_key }}';
|
|
||||||
|
|
||||||
// Should use internal renderer when no for loop
|
|
||||||
$result = $plugin->resolveLiquidVariables($template);
|
|
||||||
|
|
||||||
expect($result)->toBe('https://api.example.com/data?key=test123');
|
|
||||||
|
|
||||||
Illuminate\Support\Facades\Process::assertNothingRan();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('resolveLiquidVariables detects for loop with standard opening tag', function (): void {
|
|
||||||
Illuminate\Support\Facades\Process::fake([
|
|
||||||
'*' => Illuminate\Support\Facades\Process::result(
|
|
||||||
output: 'resolved',
|
|
||||||
exitCode: 0
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
config(['services.trmnl.liquid_enabled' => true]);
|
|
||||||
config(['services.trmnl.liquid_path' => '/usr/local/bin/trmnl-liquid-cli']);
|
|
||||||
|
|
||||||
$plugin = Plugin::factory()->create([
|
|
||||||
'preferred_renderer' => 'trmnl-liquid',
|
|
||||||
'configuration' => [],
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Test {% for pattern
|
|
||||||
$template = '{% for item in items %}test{% endfor %}';
|
|
||||||
$plugin->resolveLiquidVariables($template);
|
|
||||||
|
|
||||||
Illuminate\Support\Facades\Process::assertRan(function ($process): bool {
|
|
||||||
$command = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
||||||
|
|
||||||
return str_contains($command, 'trmnl-liquid-cli');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('resolveLiquidVariables detects for loop with whitespace stripping tag', function (): void {
|
|
||||||
Illuminate\Support\Facades\Process::fake([
|
|
||||||
'*' => Illuminate\Support\Facades\Process::result(
|
|
||||||
output: 'resolved',
|
|
||||||
exitCode: 0
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
config(['services.trmnl.liquid_enabled' => true]);
|
|
||||||
config(['services.trmnl.liquid_path' => '/usr/local/bin/trmnl-liquid-cli']);
|
|
||||||
|
|
||||||
$plugin = Plugin::factory()->create([
|
|
||||||
'preferred_renderer' => 'trmnl-liquid',
|
|
||||||
'configuration' => [],
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Test {%- for pattern (with whitespace stripping)
|
|
||||||
$template = '{%- for item in items %}test{% endfor %}';
|
|
||||||
$plugin->resolveLiquidVariables($template);
|
|
||||||
|
|
||||||
Illuminate\Support\Facades\Process::assertRan(function ($process): bool {
|
|
||||||
$command = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
||||||
|
|
||||||
return str_contains($command, 'trmnl-liquid-cli');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('updateDataPayload resolves entire polling_url field first then splits by newline', function (): void {
|
|
||||||
Http::fake([
|
|
||||||
'https://api1.example.com/data' => Http::response(['data' => 'test1'], 200),
|
|
||||||
'https://api2.example.com/data' => Http::response(['data' => 'test2'], 200),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$plugin = Plugin::factory()->create([
|
|
||||||
'data_strategy' => 'polling',
|
|
||||||
'polling_url' => "https://api1.example.com/data\nhttps://api2.example.com/data",
|
|
||||||
'polling_verb' => 'get',
|
|
||||||
'configuration' => [
|
|
||||||
'recipe_ids' => '1,2',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$plugin->updateDataPayload();
|
|
||||||
|
|
||||||
// Should have split the multi-line URL and generated two requests
|
|
||||||
expect($plugin->data_payload)->toHaveKey('IDX_0');
|
|
||||||
expect($plugin->data_payload)->toHaveKey('IDX_1');
|
|
||||||
expect($plugin->data_payload['IDX_0'])->toBe(['data' => 'test1']);
|
|
||||||
expect($plugin->data_payload['IDX_1'])->toBe(['data' => 'test2']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('updateDataPayload handles multi-line polling_url with for loop using external renderer', function (): void {
|
|
||||||
Illuminate\Support\Facades\Process::fake([
|
|
||||||
'*' => Illuminate\Support\Facades\Process::result(
|
|
||||||
output: "https://api1.example.com/data\nhttps://api2.example.com/data",
|
|
||||||
exitCode: 0
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
Http::fake([
|
|
||||||
'https://api1.example.com/data' => Http::response(['data' => 'test1'], 200),
|
|
||||||
'https://api2.example.com/data' => Http::response(['data' => 'test2'], 200),
|
|
||||||
]);
|
|
||||||
|
|
||||||
config(['services.trmnl.liquid_enabled' => true]);
|
|
||||||
config(['services.trmnl.liquid_path' => '/usr/local/bin/trmnl-liquid-cli']);
|
|
||||||
|
|
||||||
$plugin = Plugin::factory()->create([
|
|
||||||
'data_strategy' => 'polling',
|
|
||||||
'preferred_renderer' => 'trmnl-liquid',
|
|
||||||
'polling_url' => <<<'LIQUID'
|
|
||||||
{% assign ids = recipe_ids | split: "," %}
|
|
||||||
{% for id in ids %}
|
|
||||||
https://api{{ id }}.example.com/data
|
|
||||||
{% endfor %}
|
|
||||||
LIQUID
|
|
||||||
,
|
|
||||||
'polling_verb' => 'get',
|
|
||||||
'configuration' => [
|
|
||||||
'recipe_ids' => '1,2',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$plugin->updateDataPayload();
|
|
||||||
|
|
||||||
// Should have used external renderer and generated two URLs
|
|
||||||
expect($plugin->data_payload)->toHaveKey('IDX_0');
|
|
||||||
expect($plugin->data_payload)->toHaveKey('IDX_1');
|
|
||||||
expect($plugin->data_payload['IDX_0'])->toBe(['data' => 'test1']);
|
|
||||||
expect($plugin->data_payload['IDX_1'])->toBe(['data' => 'test2']);
|
|
||||||
|
|
||||||
Illuminate\Support\Facades\Process::assertRan(function ($process): bool {
|
|
||||||
$command = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
|
||||||
|
|
||||||
return str_contains($command, 'trmnl-liquid-cli');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue