Merge pull request #100 from usetrmnl/0.8.2

0.8.2
This commit is contained in:
Ikraam Ghoor
2026-05-19 11:52:30 +01:00
committed by GitHub
15 changed files with 37 additions and 38 deletions
+5
View File
@@ -1,6 +1,11 @@
# Changelog
## 0.8.2
- Fixed `framework_version: latest` rendering against the auto-upgrading `/latest/` asset path instead of the current concrete release, matching the hosted service (#99)
- Cleanup and minor improvements
## 0.8.1
### Added
+1 -1
View File
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
trmnl_preview (0.8.1)
trmnl_preview (0.8.2)
activesupport (~> 8.0)
cgi (~> 0.5)
faraday (~> 2.1)
+1 -1
View File
@@ -111,7 +111,7 @@ trmnlp build --png --color-depth 2
| `--png` | Render a PNG per view alongside the HTML |
| `--width` | PNG width in pixels (default 800) |
| `--height` | PNG height in pixels (default 480) |
| `--color-depth` | PNG bit depth — 1, 2, or 4 — overriding the markup |
| `--color-depth` | PNG bit depth — 1-8 — overriding the markup |
`--width`, `--height`, and `--color-depth` apply only with `--png`. PNG rendering needs Firefox and ImageMagick installed; plain `trmnlp build` needs neither.
+1 -1
View File
@@ -22,7 +22,7 @@ module TRMNLP
method_option :png, type: :boolean, default: false, desc: 'Also render a PNG per view'
method_option :width, type: :numeric, desc: 'PNG width in pixels (with --png)'
method_option :height, type: :numeric, desc: 'PNG height in pixels (with --png)'
method_option :color_depth, type: :numeric, desc: 'PNG bit depth: 1, 2, or 4 (with --png)'
method_option :color_depth, type: :numeric, desc: 'PNG bit depth: 1-8 (with --png)'
def build
Commands::Build.run(options)
end
+6 -8
View File
@@ -44,9 +44,13 @@ module TRMNLP
def pinned? = @pinned
def css_url = "#{@asset_host}/css/#{path_segment}/plugins.css"
# Both a pinned and an unpinned ("latest") version resolve to a
# concrete release here — #number is never the literal "latest" — so a
# local preview renders the same bundle as the hosted service instead
# of drifting onto a new release the moment one ships.
def css_url = "#{@asset_host}/css/#{number}/plugins.css"
def js_url = "#{@asset_host}/js/#{path_segment}/plugins.js"
def js_url = "#{@asset_host}/js/#{number}/plugins.js"
def ==(other) = other.is_a?(self.class) && number == other.number
@@ -59,11 +63,5 @@ module TRMNLP
def as_json(*) = number
def to_s = number
private
# When pinned, requests assets at /css/<version>/plugins.css to lock
# behavior; otherwise hit /css/latest/ for live updates.
def path_segment = pinned? ? @number : 'latest'
end
end
@@ -24,8 +24,8 @@ module TRMNLP
def pass? = static_image_urls.all? { |url| reachable?(url) }
def static_image_urls
source.view_markup.values
.flat_map { |html| html.scan(/<img[^>]+src\s*=\s*["']([^"']+)["']/i).flatten }
source.all_markup
.scan(/<img[^>]+src\s*=\s*["']([^"']+)["']/i).flatten
.map(&:strip)
.reject { |src| src.empty? || src.include?('{{') || src.start_with?('data:') }
end
+2 -1
View File
@@ -6,7 +6,8 @@ module TRMNLP
module Lint
module Checks
class WaitsForDomLoad < Check
MESSAGE = 'JavaScript should listen for the DOMContentLoaded event, not window.onLoad()'
MESSAGE = 'JavaScript should listen for the DOMContentLoaded event, ' \
'not window.onload or window.addEventListener("load")'
LEARN_MORE = 'https://help.trmnl.com/en/articles/9510536-private-plugins#h_db7030f8b8'
FORBIDDEN = ['window.onload', 'window.addeventlistener("load")',
"window.addeventlistener('load')"].freeze
+1 -1
View File
@@ -72,7 +72,7 @@ module TRMNLP
end
end
# bindings must match the `GET /render/{view}.html` route in app.rb
# ivars must match the @-references in web/views/render_html.erb
class TemplateBinding
def initialize(renderer, view, params)
@view = view
+1 -3
View File
@@ -59,9 +59,7 @@ module TRMNLP
TransformClient::Result.new(
stdout: parsed['stdout'] || '',
stderr: parsed['stderr'] || '',
# Fall back to stdout for daemons that haven't been upgraded to
# the separate `output` channel yet.
output: (parsed['output'].to_s.empty? ? parsed['stdout'] : parsed['output']).to_s,
output: parsed['output'].to_s,
exit_code: parsed['exit_code'] || 0,
duration_ms: parsed['duration_ms'] || 0,
error: parsed['error']
+4 -2
View File
@@ -9,8 +9,10 @@ module TRMNLP
# snippet supplied by the caller. Subprocess writes to a tempfile
# path, Http writes to FD 3 for the production daemon to capture.
#
# Mirrors the hosted serverless runtime's code-wrapping behavior
# verbatim except for the configurable sink.
# Mirrors the hosted serverless runtime's code-wrapping behavior,
# parameterized on the per-backend output sink. PHP additionally
# carries a leading `<?php` tag the file-based Subprocess path
# needs; the daemon tolerates it, so parity holds.
#
# NOTE: `output_sink` is spliced verbatim into the generated script as
# executable code. It MUST be trmnlp-generated (see Subprocess#sink_for
+3 -3
View File
@@ -17,9 +17,9 @@ module TRMNLP
# Assembles the merged data hash. The trmnl namespace is built first,
# layered with static_data / cached polled data / user_data_overrides,
# then piped through the transform. The trmnl namespace is re-applied
# after the transform so device, user, and plugin_settings survive
# even when the transform doesn't pass them through.
# then piped through the transform. The whole trmnl namespace is
# re-applied after the transform so it survives even when the
# transform doesn't pass it through.
def call(device: {})
namespace = base_trmnl_data(device:)
merged = assemble(namespace)
+1 -1
View File
@@ -1,5 +1,5 @@
# frozen_string_literal: true
module TRMNLP
VERSION = '0.8.1'
VERSION = '0.8.2'
end
+8 -3
View File
@@ -20,9 +20,14 @@ RSpec.describe TRMNLP::FrameworkVersion do
context 'when version is "latest"' do
subject(:framework) { described_class.new('latest') }
it 'serves from the /latest/ path so updates are live' do
expect(framework.css_url).to eq('https://trmnl.com/css/latest/plugins.css')
expect(framework.js_url).to eq('https://trmnl.com/js/latest/plugins.js')
# NOTE: "latest" resolves to a concrete version rather than the
# auto-upgrading /latest/ CDN path, matching the hosted service —
# otherwise a local preview silently drifts when a new release ships.
it 'serves from the concrete current version' do
number = described_class.latest.number
expect(framework.css_url).to eq("https://trmnl.com/css/#{number}/plugins.css")
expect(framework.js_url).to eq("https://trmnl.com/js/#{number}/plugins.js")
end
end
@@ -7,7 +7,7 @@ require 'trmnlp/lint/checks/image_links_reachable'
RSpec.describe TRMNLP::Lint::Checks::ImageLinksReachable do
subject(:check) { described_class.new(source) }
let(:source) { instance_double(TRMNLP::Lint::Source, view_markup: { 'full' => markup }) }
let(:source) { instance_double(TRMNLP::Lint::Source, all_markup: markup) }
let(:markup) { '<img src="https://example.com/logo.png">' }
describe '#issues' do
@@ -80,16 +80,6 @@ RSpec.describe TRMNLP::TransformBackend::Http do
expect(result).to be_success
end
it 'falls back to stdout when output field is absent (older daemons)' do
stubs.post('/execute') do
[200, {}, '{"stdout":"{\"legacy\":true}","stderr":"","exit_code":0,"duration_ms":7,"error":null}']
end
result = backend.execute(**payload)
expect(result.output).to eq('{"legacy":true}')
end
it 'returns a failure Result when the daemon responds non-200' do
stubs.post('/execute') { [500, {}, 'kaboom'] }