trmnl_preview 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +89 -1
- data/bin/trmnlp +2 -1
- data/lib/trmnlp/app.rb +10 -0
- data/lib/trmnlp/cli.rb +4 -0
- data/lib/trmnlp/commands/base.rb +1 -1
- data/lib/trmnlp/commands/build.rb +48 -6
- data/lib/trmnlp/commands/init.rb +4 -0
- data/lib/trmnlp/commands/serve.rb +2 -15
- data/lib/trmnlp/firefox_driver.rb +27 -0
- data/lib/trmnlp/framework_version.rb +6 -8
- data/lib/trmnlp/lint/checks/image_links_reachable.rb +2 -2
- data/lib/trmnlp/lint/checks/waits_for_dom_load.rb +2 -1
- data/lib/trmnlp/poller.rb +3 -3
- data/lib/trmnlp/renderer.rb +1 -1
- data/lib/trmnlp/reporter.rb +5 -1
- data/lib/trmnlp/screenshot.rb +17 -4
- data/lib/trmnlp/transform_backend/http.rb +1 -3
- data/lib/trmnlp/transform_backend/wrapper.rb +4 -2
- data/lib/trmnlp/user_data_assembler.rb +3 -3
- data/lib/trmnlp/version.rb +1 -1
- data/web/public/index.css +5 -0
- data/web/views/index.erb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 236a5502b02ab161fffc5330fc712c329810f877038c71baadc86b376e607157
|
|
4
|
+
data.tar.gz: 41400bbbe0b54639311ae928280277ee1717f58f1de74bd71ab92f213bbe92db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 604a7f74231c2e738dbf61995469a64e488cc1d69fa9eb84076fb9badfb2d83fd8c490fa261154471852e7c7effb6071e458d40e748bc70de7ea27455d5a8485
|
|
7
|
+
data.tar.gz: c4396159f68882180618578725f2e386e703a9d992a195a5ce2de463d8dab407d9b0a30655495963e0b1af35ee2ab0813fae75ed77633d616312fa64246ef753
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
|
|
2
2
|
# Changelog
|
|
3
3
|
|
|
4
|
+
## 0.8.2
|
|
5
|
+
|
|
6
|
+
- Fixed `framework_version: latest` rendering against the auto-upgrading `/latest/` asset path instead of the current concrete release, matching the hosted service (#99)
|
|
7
|
+
- Cleanup and minor improvements
|
|
8
|
+
|
|
9
|
+
## 0.8.1
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `trmnlp build --png` renders a PNG for every view alongside the HTML, with `--width`, `--height`, and `--color-depth` flags to override the defaults (#92)
|
|
14
|
+
- Colour-coded the preview's payload-size badge — yellow from 75 KB, red from 100 KB — so an oversized merge-variable payload is visible at a glance (#67)
|
|
15
|
+
- Added colour to CLI output — `lint` results, warnings, and errors — suppressed automatically when output is piped or redirected (#33)
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- `trmnlp init` no longer produces read-only project files when trmnlp itself is installed read-only, such as on NixOS (#83)
|
|
20
|
+
- Non-JSON polling responses (`text/html`, `text/plain`) are exposed to templates as `{{ data }}`, matching the hosted service — previously `{{ text }}` (#81)
|
|
21
|
+
|
|
22
|
+
### Housekeeping
|
|
23
|
+
|
|
24
|
+
- Added SimpleCov coverage tracking, gated in CI at a 90% floor, plus dedicated specs for every lint check
|
|
25
|
+
- Extracted the headless-Firefox driver into a shared `FirefoxDriver` module used by both `serve` and `build --png`
|
|
26
|
+
|
|
4
27
|
## 0.8.0
|
|
5
28
|
|
|
6
29
|
### Housekeeping
|
data/README.md
CHANGED
|
@@ -80,7 +80,7 @@ trmnlp push # upload
|
|
|
80
80
|
|---|---|
|
|
81
81
|
| `trmnlp init NAME` | Start a new plugin project |
|
|
82
82
|
| `trmnlp serve` | Start a local dev server |
|
|
83
|
-
| `trmnlp build` | Generate static HTML files |
|
|
83
|
+
| `trmnlp build` | Generate static HTML files, or PNGs with `--png` |
|
|
84
84
|
| `trmnlp lint` | Check plugin code against TRMNL best practices |
|
|
85
85
|
| `trmnlp login` | Authenticate with TRMNL server |
|
|
86
86
|
| `trmnlp list` | List private plugins from TRMNL server |
|
|
@@ -91,12 +91,83 @@ trmnlp push # upload
|
|
|
91
91
|
|
|
92
92
|
`trmnlp lint` exits non-zero when it finds issues, so you can gate CI on it. Run `trmnlp help` for all flags.
|
|
93
93
|
|
|
94
|
+
## Building Static Files
|
|
95
|
+
|
|
96
|
+
`trmnlp build` renders every view to a static file under `_build/` — handy for exporting a snapshot or feeding the output into another pipeline. Run it from inside a plugin project:
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
trmnlp build # writes _build/full.html, _build/half_horizontal.html, ...
|
|
100
|
+
trmnlp build --png # also writes a PNG for each view
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`--png` renders each view through the same screenshot pipeline `serve` uses. By default a PNG is 800×480 at the bit depth declared by the markup's `screen--Nbit` class (1-bit if none). Override any of those:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
trmnlp build --png --color-depth 2
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
| Flag | Purpose |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `--png` | Render a PNG per view alongside the HTML |
|
|
112
|
+
| `--width` | PNG width in pixels (default 800) |
|
|
113
|
+
| `--height` | PNG height in pixels (default 480) |
|
|
114
|
+
| `--color-depth` | PNG bit depth — 1-8 — overriding the markup |
|
|
115
|
+
|
|
116
|
+
`--width`, `--height`, and `--color-depth` apply only with `--png`. PNG rendering needs Firefox and ImageMagick installed; plain `trmnlp build` needs neither.
|
|
117
|
+
|
|
94
118
|
## Authentication
|
|
95
119
|
|
|
96
120
|
The `trmnlp login` command saves your API key to `~/.config/trmnlp/config.yml`.
|
|
97
121
|
|
|
98
122
|
If an environment variable is more convenient (for example in a CI/CD pipeline), you can set `$TRMNL_API_KEY` instead.
|
|
99
123
|
|
|
124
|
+
## Continuous Integration
|
|
125
|
+
|
|
126
|
+
`trmnlp` runs in GitHub Actions without `trmnlp login` — set the `TRMNL_API_KEY`
|
|
127
|
+
environment variable and it's used in place of the saved config. Add it as a
|
|
128
|
+
repository secret, then drop this into `.github/workflows/trmnl.yml`:
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
name: TRMNL
|
|
132
|
+
on:
|
|
133
|
+
pull_request:
|
|
134
|
+
push:
|
|
135
|
+
branches: [main]
|
|
136
|
+
|
|
137
|
+
jobs:
|
|
138
|
+
lint:
|
|
139
|
+
runs-on: ubuntu-latest
|
|
140
|
+
steps:
|
|
141
|
+
- uses: actions/checkout@v6
|
|
142
|
+
- uses: ruby/setup-ruby@v1
|
|
143
|
+
with:
|
|
144
|
+
ruby-version: "4.0"
|
|
145
|
+
- run: gem install trmnl_preview
|
|
146
|
+
- run: trmnlp lint
|
|
147
|
+
|
|
148
|
+
push:
|
|
149
|
+
needs: lint
|
|
150
|
+
if: github.ref == 'refs/heads/main'
|
|
151
|
+
runs-on: ubuntu-latest
|
|
152
|
+
steps:
|
|
153
|
+
- uses: actions/checkout@v6
|
|
154
|
+
- uses: ruby/setup-ruby@v1
|
|
155
|
+
with:
|
|
156
|
+
ruby-version: "4.0"
|
|
157
|
+
- run: gem install trmnl_preview
|
|
158
|
+
- run: trmnlp push --force
|
|
159
|
+
env:
|
|
160
|
+
TRMNL_API_KEY: ${{ secrets.TRMNL_API_KEY }}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The `lint` job gates every pull request — `trmnlp lint` exits non-zero on
|
|
164
|
+
issues, so a failing check blocks the merge. The `push` job uploads to TRMNL
|
|
165
|
+
only on `main`.
|
|
166
|
+
|
|
167
|
+
> **Make sure `src/settings.yml` has an `id`.** `trmnlp push` updates the
|
|
168
|
+
> plugin with that id; without one it creates a *new* plugin on every run.
|
|
169
|
+
> Projects made with `trmnlp clone` or `trmnlp pull` already have it.
|
|
170
|
+
|
|
100
171
|
## Running trmnlp
|
|
101
172
|
|
|
102
173
|
The `bin/trmnlp` script is provided as a convenience. It will use the local Ruby gem if available, falling back to the `trmnl/trmnlp` Docker image.
|
|
@@ -310,6 +381,23 @@ To test, run:
|
|
|
310
381
|
bin/rake
|
|
311
382
|
```
|
|
312
383
|
|
|
384
|
+
Specs run under SimpleCov; a coverage report is written to `coverage/`.
|
|
385
|
+
|
|
386
|
+
## Releasing
|
|
387
|
+
|
|
388
|
+
Releases are automated. The [`Release` workflow](.github/workflows/release.yaml)
|
|
389
|
+
fires whenever `lib/trmnlp/version.rb` changes on `main`, then tags the commit,
|
|
390
|
+
publishes the gem to RubyGems, and pushes the multi-arch Docker image. Each step
|
|
391
|
+
is idempotent, so the workflow is safe to re-run after a partial failure.
|
|
392
|
+
|
|
393
|
+
To cut a release:
|
|
394
|
+
|
|
395
|
+
1. Bump the version in `lib/trmnlp/version.rb`.
|
|
396
|
+
2. Run `bundle install` so `Gemfile.lock` picks up the new version.
|
|
397
|
+
3. Commit and merge to `main` — the workflow does the rest.
|
|
398
|
+
|
|
399
|
+
By convention, add a matching `CHANGELOG.md` entry in the same change.
|
|
400
|
+
|
|
313
401
|
## Contributing
|
|
314
402
|
|
|
315
403
|
Bug reports and pull requests are welcome on GitHub at https://github.com/usetrmnl/trmnlp.
|
data/bin/trmnlp
CHANGED
data/lib/trmnlp/app.rb
CHANGED
|
@@ -18,6 +18,16 @@ module TRMNLP
|
|
|
18
18
|
bytes < 1024 ? "#{bytes} bytes" : format('%.1f KB', bytes / 1024.0)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
# Colour-codes the payload badge so an author notices when merge
|
|
22
|
+
# variables approach the size the hosted service starts rejecting.
|
|
23
|
+
# KB = 1024, matching format_bytes.
|
|
24
|
+
def payload_size_class(bytes)
|
|
25
|
+
return 'payload-size--over' if bytes >= 100 * 1024
|
|
26
|
+
return 'payload-size--warn' if bytes >= 75 * 1024
|
|
27
|
+
|
|
28
|
+
'payload-size--ok'
|
|
29
|
+
end
|
|
30
|
+
|
|
21
31
|
# NOTE: render_html.erb's layout yields raw plugin HTML through `<%= yield %>`,
|
|
22
32
|
# so a global `escape_html` setting would corrupt the render. Escape per-value.
|
|
23
33
|
def h(text)
|
data/lib/trmnlp/cli.rb
CHANGED
|
@@ -19,6 +19,10 @@ module TRMNLP
|
|
|
19
19
|
def self.default_bind = File.exist?('/.dockerenv') ? '0.0.0.0' : '127.0.0.1'
|
|
20
20
|
|
|
21
21
|
desc 'build', 'Generate static HTML files'
|
|
22
|
+
method_option :png, type: :boolean, default: false, desc: 'Also render a PNG per view'
|
|
23
|
+
method_option :width, type: :numeric, desc: 'PNG width in pixels (with --png)'
|
|
24
|
+
method_option :height, type: :numeric, desc: 'PNG height in pixels (with --png)'
|
|
25
|
+
method_option :color_depth, type: :numeric, desc: 'PNG bit depth: 1-8 (with --png)'
|
|
22
26
|
def build
|
|
23
27
|
Commands::Build.run(options)
|
|
24
28
|
end
|
data/lib/trmnlp/commands/base.rb
CHANGED
|
@@ -50,7 +50,7 @@ module TRMNLP
|
|
|
50
50
|
# plugin author notices before the field misbehaves in production.
|
|
51
51
|
def report_form_field_warnings
|
|
52
52
|
FormField.validate_all(config.plugin.custom_field_definitions).each do |warning|
|
|
53
|
-
reporter.info("warning: settings.yml custom_fields — #{warning}")
|
|
53
|
+
reporter.info(reporter.yellow("warning: settings.yml custom_fields — #{warning}"))
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
3
5
|
require_relative 'base'
|
|
6
|
+
require_relative '../browser_pool'
|
|
7
|
+
require_relative '../firefox_driver'
|
|
8
|
+
require_relative '../screen_generator'
|
|
9
|
+
require_relative '../screenshot'
|
|
4
10
|
|
|
5
11
|
module TRMNLP
|
|
6
12
|
module Commands
|
|
7
13
|
class Build < Base
|
|
8
|
-
Options = Data.define(:dir, :quiet)
|
|
14
|
+
Options = Data.define(:dir, :quiet, :png, :width, :height, :color_depth)
|
|
9
15
|
|
|
10
16
|
def call
|
|
11
17
|
context.validate!
|
|
@@ -13,13 +19,49 @@ module TRMNLP
|
|
|
13
19
|
context.poller.poll_data
|
|
14
20
|
context.paths.create_build_dir
|
|
15
21
|
|
|
16
|
-
Screen.all.each
|
|
17
|
-
output_path = context.paths.build_dir.join("#{screen.name}.html")
|
|
18
|
-
reporter.info "Writing #{output_path}..."
|
|
19
|
-
output_path.write(context.renderer.render_full_page(screen.name))
|
|
20
|
-
end
|
|
22
|
+
Screen.all.each { |screen| build_screen(screen) }
|
|
21
23
|
|
|
22
24
|
reporter.info 'Done!'
|
|
25
|
+
ensure
|
|
26
|
+
@browser_pool&.shutdown
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def build_screen(screen)
|
|
32
|
+
html = context.renderer.render_full_page(screen.name)
|
|
33
|
+
write_html(screen.name, html)
|
|
34
|
+
write_png(screen.name, html) if options.png
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def write_html(view, html)
|
|
38
|
+
path = context.paths.build_dir.join("#{view}.html")
|
|
39
|
+
reporter.info "Writing #{path}..."
|
|
40
|
+
path.write(html)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# --png is additive: the HTML is rendered either way, so it stays on
|
|
44
|
+
# disk alongside the PNG rather than being replaced by it.
|
|
45
|
+
def write_png(view, html)
|
|
46
|
+
path = context.paths.build_dir.join("#{view}.png")
|
|
47
|
+
reporter.info "Writing #{path}..."
|
|
48
|
+
image = screen_generator(html).process
|
|
49
|
+
FileUtils.cp(image.path, path)
|
|
50
|
+
ensure
|
|
51
|
+
image&.close!
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# --width/--height/--color-depth are optional; nil lets ScreenGenerator
|
|
55
|
+
# fall back to 800x480 and the screen--Nbit depth sniffed from the markup.
|
|
56
|
+
def screen_generator(html)
|
|
57
|
+
ScreenGenerator.new(html, screenshot:, width: options.width,
|
|
58
|
+
height: options.height, color_depth: options.color_depth)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def screenshot = @screenshot ||= Screenshot.new(pool: browser_pool)
|
|
62
|
+
|
|
63
|
+
def browser_pool
|
|
64
|
+
@browser_pool ||= BrowserPool.new(driver_factory: FirefoxDriver.method(:build))
|
|
23
65
|
end
|
|
24
66
|
end
|
|
25
67
|
end
|
data/lib/trmnlp/commands/init.rb
CHANGED
|
@@ -35,6 +35,10 @@ module TRMNLP
|
|
|
35
35
|
|
|
36
36
|
reporter.info "Creating #{destination_pathname}"
|
|
37
37
|
FileUtils.cp(source_pathname, destination_pathname)
|
|
38
|
+
# NOTE: cp preserves the source mode. Templates installed read-only
|
|
39
|
+
# (e.g. NixOS /nix/store is 0444) would leave the author unable to
|
|
40
|
+
# edit their own project. Add owner-write; keep any exec bit.
|
|
41
|
+
destination_pathname.chmod(destination_pathname.stat.mode | 0o200)
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
reporter.info <<~HEREDOC
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'selenium-webdriver'
|
|
4
|
-
|
|
5
3
|
require_relative 'base'
|
|
6
4
|
require_relative '../api_client'
|
|
7
5
|
require_relative '../browser_pool'
|
|
6
|
+
require_relative '../firefox_driver'
|
|
8
7
|
|
|
9
8
|
module TRMNLP
|
|
10
9
|
module Commands
|
|
@@ -20,7 +19,7 @@ module TRMNLP
|
|
|
20
19
|
|
|
21
20
|
# Now we can configure things
|
|
22
21
|
App.set(:context, context)
|
|
23
|
-
App.set(:browser_pool, BrowserPool.new(driver_factory: method(:
|
|
22
|
+
App.set(:browser_pool, BrowserPool.new(driver_factory: FirefoxDriver.method(:build)))
|
|
24
23
|
App.set(:bind, options.bind)
|
|
25
24
|
App.set(:port, options.port)
|
|
26
25
|
permit_all_hosts if codespaces?
|
|
@@ -37,18 +36,6 @@ module TRMNLP
|
|
|
37
36
|
def permit_all_hosts
|
|
38
37
|
App.set(:host_authorization, { allow_if: ->(_env) { true } })
|
|
39
38
|
end
|
|
40
|
-
|
|
41
|
-
def build_firefox_driver
|
|
42
|
-
options = Selenium::WebDriver::Firefox::Options.new
|
|
43
|
-
options.add_argument('--headless')
|
|
44
|
-
options.add_argument('--disable-web-security')
|
|
45
|
-
# Disable subpixel antialiasing — its colour fringing quantizes badly on 1-bit e-ink.
|
|
46
|
-
options.add_preference('gfx.text.disable-aa', true)
|
|
47
|
-
options.add_preference('gfx.text.subpixel-position.force-disabled', true)
|
|
48
|
-
Selenium::WebDriver.for(:firefox, options: options).tap do |driver|
|
|
49
|
-
driver.manage.window.maximize
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
39
|
end
|
|
53
40
|
end
|
|
54
41
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'selenium-webdriver'
|
|
4
|
+
|
|
5
|
+
module TRMNLP
|
|
6
|
+
# Builds the headless Firefox driver that screenshots rendered plugins.
|
|
7
|
+
# Shared by `trmnlp serve` (the PNG preview route) and `trmnlp build --png`.
|
|
8
|
+
module FirefoxDriver
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def build
|
|
12
|
+
Selenium::WebDriver.for(:firefox, options:).tap do |driver|
|
|
13
|
+
driver.manage.window.maximize
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def options
|
|
18
|
+
Selenium::WebDriver::Firefox::Options.new.tap do |opts|
|
|
19
|
+
opts.add_argument('--headless')
|
|
20
|
+
opts.add_argument('--disable-web-security')
|
|
21
|
+
# Subpixel antialiasing colour-fringes badly when quantized to 1-bit e-ink.
|
|
22
|
+
opts.add_preference('gfx.text.disable-aa', true)
|
|
23
|
+
opts.add_preference('gfx.text.subpixel-position.force-disabled', true)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -44,9 +44,13 @@ module TRMNLP
|
|
|
44
44
|
|
|
45
45
|
def pinned? = @pinned
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
# Both a pinned and an unpinned ("latest") version resolve to a
|
|
48
|
+
# concrete release here — #number is never the literal "latest" — so a
|
|
49
|
+
# local preview renders the same bundle as the hosted service instead
|
|
50
|
+
# of drifting onto a new release the moment one ships.
|
|
51
|
+
def css_url = "#{@asset_host}/css/#{number}/plugins.css"
|
|
48
52
|
|
|
49
|
-
def js_url = "#{@asset_host}/js/#{
|
|
53
|
+
def js_url = "#{@asset_host}/js/#{number}/plugins.js"
|
|
50
54
|
|
|
51
55
|
def ==(other) = other.is_a?(self.class) && number == other.number
|
|
52
56
|
|
|
@@ -59,11 +63,5 @@ module TRMNLP
|
|
|
59
63
|
def as_json(*) = number
|
|
60
64
|
|
|
61
65
|
def to_s = number
|
|
62
|
-
|
|
63
|
-
private
|
|
64
|
-
|
|
65
|
-
# When pinned, requests assets at /css/<version>/plugins.css to lock
|
|
66
|
-
# behavior; otherwise hit /css/latest/ for live updates.
|
|
67
|
-
def path_segment = pinned? ? @number : 'latest'
|
|
68
66
|
end
|
|
69
67
|
end
|
|
@@ -24,8 +24,8 @@ module TRMNLP
|
|
|
24
24
|
def pass? = static_image_urls.all? { |url| reachable?(url) }
|
|
25
25
|
|
|
26
26
|
def static_image_urls
|
|
27
|
-
source.
|
|
28
|
-
.
|
|
27
|
+
source.all_markup
|
|
28
|
+
.scan(/<img[^>]+src\s*=\s*["']([^"']+)["']/i).flatten
|
|
29
29
|
.map(&:strip)
|
|
30
30
|
.reject { |src| src.empty? || src.include?('{{') || src.start_with?('data:') }
|
|
31
31
|
end
|
|
@@ -6,7 +6,8 @@ module TRMNLP
|
|
|
6
6
|
module Lint
|
|
7
7
|
module Checks
|
|
8
8
|
class WaitsForDomLoad < Check
|
|
9
|
-
MESSAGE = 'JavaScript should listen for the DOMContentLoaded event,
|
|
9
|
+
MESSAGE = 'JavaScript should listen for the DOMContentLoaded event, ' \
|
|
10
|
+
'not window.onload or window.addEventListener("load")'
|
|
10
11
|
LEARN_MORE = 'https://help.trmnl.com/en/articles/9510536-private-plugins#h_db7030f8b8'
|
|
11
12
|
FORBIDDEN = ['window.onload', 'window.addeventlistener("load")',
|
|
12
13
|
"window.addeventlistener('load')"].freeze
|
data/lib/trmnlp/poller.rb
CHANGED
|
@@ -25,7 +25,7 @@ module TRMNLP
|
|
|
25
25
|
# and keep the preview server alive, not crash the user's session. We
|
|
26
26
|
# deliberately swallow here and return {} so the renderer keeps rendering.
|
|
27
27
|
rescue StandardError => e
|
|
28
|
-
reporter.info("warning: #{e.message}")
|
|
28
|
+
reporter.info(reporter.yellow("warning: #{e.message}"))
|
|
29
29
|
{}
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -34,7 +34,7 @@ module TRMNLP
|
|
|
34
34
|
# NOTE: Same rationale as #poll_data — a bad webhook payload shouldn't take
|
|
35
35
|
# down the dev server. Report a warning and keep serving.
|
|
36
36
|
rescue StandardError => e
|
|
37
|
-
reporter.info("webhook warning: #{e.message}")
|
|
37
|
+
reporter.info(reporter.yellow("webhook warning: #{e.message}"))
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
private
|
|
@@ -76,7 +76,7 @@ module TRMNLP
|
|
|
76
76
|
case content_type
|
|
77
77
|
when 'application/json', %r{^application/.+\+json} then wrap_array(JSON.parse(body))
|
|
78
78
|
when 'text/xml', 'application/xml', %r{^application/.+\+xml} then wrap_array(Hash.from_xml(body))
|
|
79
|
-
when 'text/html', 'text/plain' then sniff_json(body) || { '
|
|
79
|
+
when 'text/html', 'text/plain' then sniff_json(body) || { 'data' => body }
|
|
80
80
|
else log_unknown_type(content_type_header)
|
|
81
81
|
end
|
|
82
82
|
end
|
data/lib/trmnlp/renderer.rb
CHANGED
data/lib/trmnlp/reporter.rb
CHANGED
|
@@ -11,6 +11,9 @@ module TRMNLP
|
|
|
11
11
|
@quiet = quiet
|
|
12
12
|
@stream = stream
|
|
13
13
|
@messages = []
|
|
14
|
+
# Colour only when the stream is a real terminal, so ANSI codes
|
|
15
|
+
# never leak into piped or redirected output.
|
|
16
|
+
@tty = stream.tty?
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
def info(message)
|
|
@@ -20,9 +23,10 @@ module TRMNLP
|
|
|
20
23
|
|
|
21
24
|
def green(text) = colorize(text, 32)
|
|
22
25
|
def yellow(text) = colorize(text, 33)
|
|
26
|
+
def red(text) = colorize(text, 31)
|
|
23
27
|
|
|
24
28
|
private
|
|
25
29
|
|
|
26
|
-
def colorize(text, code) = "\e[#{code}m#{text}\e[0m"
|
|
30
|
+
def colorize(text, code) = @tty ? "\e[#{code}m#{text}\e[0m" : text
|
|
27
31
|
end
|
|
28
32
|
end
|
data/lib/trmnlp/screenshot.rb
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
require 'selenium-webdriver'
|
|
4
4
|
require 'tempfile'
|
|
5
5
|
|
|
6
|
+
require_relative 'errors'
|
|
7
|
+
|
|
6
8
|
module TRMNLP
|
|
7
9
|
class Screenshot
|
|
8
|
-
def initialize(pool:)
|
|
10
|
+
def initialize(pool:, viewport_timeout: 5)
|
|
9
11
|
@pool = pool
|
|
12
|
+
@viewport_timeout = viewport_timeout
|
|
10
13
|
end
|
|
11
14
|
|
|
12
15
|
def call(html:, width:, height:)
|
|
@@ -50,15 +53,25 @@ module TRMNLP
|
|
|
50
53
|
# NOTE: A cold Firefox — e.g. the first render after the container boots —
|
|
51
54
|
# applies a window resize lazily. The old fixed sleep raced that reflow and
|
|
52
55
|
# clipped the first screenshot short (800x433 instead of 800x480). Poll the
|
|
53
|
-
# real viewport instead, re-applying the size until it lands
|
|
54
|
-
#
|
|
56
|
+
# real viewport instead, re-applying the size until it lands. A width the
|
|
57
|
+
# browser refuses to honour (below its ~500px window minimum) never settles;
|
|
58
|
+
# that surfaces as a clear RenderError rather than an opaque, retried timeout.
|
|
55
59
|
def wait_for_viewport(driver, width, height)
|
|
56
|
-
Selenium::WebDriver::Wait.new(timeout:
|
|
60
|
+
Selenium::WebDriver::Wait.new(timeout: @viewport_timeout, interval: 0.1).until do
|
|
57
61
|
next true if viewport(driver) == [width, height]
|
|
58
62
|
|
|
59
63
|
apply_window_size(driver, width, height)
|
|
60
64
|
false
|
|
61
65
|
end
|
|
66
|
+
rescue Selenium::WebDriver::Error::TimeoutError
|
|
67
|
+
raise RenderError, viewport_clamp_message(driver, width, height)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def viewport_clamp_message(driver, width, height)
|
|
71
|
+
actual_width, actual_height = viewport(driver)
|
|
72
|
+
"Could not render at #{width}x#{height}: the browser clamped the viewport " \
|
|
73
|
+
"to #{actual_width}x#{actual_height}. PNG rendering needs a width of " \
|
|
74
|
+
'roughly 500px or more — headless Firefox will not size its window narrower.'
|
|
62
75
|
end
|
|
63
76
|
|
|
64
77
|
def viewport(driver)
|
|
@@ -59,9 +59,7 @@ module TRMNLP
|
|
|
59
59
|
TransformClient::Result.new(
|
|
60
60
|
stdout: parsed['stdout'] || '',
|
|
61
61
|
stderr: parsed['stderr'] || '',
|
|
62
|
-
|
|
63
|
-
# the separate `output` channel yet.
|
|
64
|
-
output: (parsed['output'].to_s.empty? ? parsed['stdout'] : parsed['output']).to_s,
|
|
62
|
+
output: parsed['output'].to_s,
|
|
65
63
|
exit_code: parsed['exit_code'] || 0,
|
|
66
64
|
duration_ms: parsed['duration_ms'] || 0,
|
|
67
65
|
error: parsed['error']
|
|
@@ -9,8 +9,10 @@ module TRMNLP
|
|
|
9
9
|
# snippet supplied by the caller. Subprocess writes to a tempfile
|
|
10
10
|
# path, Http writes to FD 3 for the production daemon to capture.
|
|
11
11
|
#
|
|
12
|
-
# Mirrors the hosted serverless runtime's code-wrapping behavior
|
|
13
|
-
#
|
|
12
|
+
# Mirrors the hosted serverless runtime's code-wrapping behavior,
|
|
13
|
+
# parameterized on the per-backend output sink. PHP additionally
|
|
14
|
+
# carries a leading `<?php` tag the file-based Subprocess path
|
|
15
|
+
# needs; the daemon tolerates it, so parity holds.
|
|
14
16
|
#
|
|
15
17
|
# NOTE: `output_sink` is spliced verbatim into the generated script as
|
|
16
18
|
# executable code. It MUST be trmnlp-generated (see Subprocess#sink_for
|
|
@@ -17,9 +17,9 @@ module TRMNLP
|
|
|
17
17
|
|
|
18
18
|
# Assembles the merged data hash. The trmnl namespace is built first,
|
|
19
19
|
# layered with static_data / cached polled data / user_data_overrides,
|
|
20
|
-
# then piped through the transform. The trmnl namespace is
|
|
21
|
-
# after the transform so
|
|
22
|
-
#
|
|
20
|
+
# then piped through the transform. The whole trmnl namespace is
|
|
21
|
+
# re-applied after the transform so it survives even when the
|
|
22
|
+
# transform doesn't pass it through.
|
|
23
23
|
def call(device: {})
|
|
24
24
|
namespace = base_trmnl_data(device:)
|
|
25
25
|
merged = assemble(namespace)
|
data/lib/trmnlp/version.rb
CHANGED
data/web/public/index.css
CHANGED
|
@@ -110,6 +110,11 @@ pre > code {
|
|
|
110
110
|
margin: 0.5em 0 0.2em;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/* A coloured badge is a warning — full opacity so the colour reads true. */
|
|
114
|
+
.payload-size--ok { color: #1a7f37; opacity: 1; }
|
|
115
|
+
.payload-size--warn { color: #bf8700; opacity: 1; }
|
|
116
|
+
.payload-size--over { color: #d1242f; opacity: 1; }
|
|
117
|
+
|
|
113
118
|
@media (prefers-color-scheme: dark) {
|
|
114
119
|
:root {
|
|
115
120
|
--bg-color: #191b21;
|
data/web/views/index.erb
CHANGED
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
|
|
67
67
|
<iframe style="width: 800px; height: 480px; border: 1px solid black; background: white;">Rendering…</iframe>
|
|
68
68
|
|
|
69
|
-
<div class="payload-size">Payload: <%= format_bytes(@payload_size) %></div>
|
|
69
|
+
<div class="payload-size <%= payload_size_class(@payload_size) %>">Payload: <%= format_bytes(@payload_size) %></div>
|
|
70
70
|
<pre id="user-data"><code><%= h @user_data %></code></pre>
|
|
71
71
|
</main>
|
|
72
72
|
</body>
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: trmnl_preview
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rockwell Schrock
|
|
@@ -284,6 +284,7 @@ files:
|
|
|
284
284
|
- lib/trmnlp/config/project.rb
|
|
285
285
|
- lib/trmnlp/context.rb
|
|
286
286
|
- lib/trmnlp/errors.rb
|
|
287
|
+
- lib/trmnlp/firefox_driver.rb
|
|
287
288
|
- lib/trmnlp/form_field.rb
|
|
288
289
|
- lib/trmnlp/framework_version.rb
|
|
289
290
|
- lib/trmnlp/image_quantizer.rb
|