trmnl_preview 0.7.2 → 0.8.0

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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +192 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +319 -0
  5. data/bin/rake +27 -0
  6. data/bin/trmnlp +13 -14
  7. data/db/data/form_fields.yml +24 -0
  8. data/db/data/framework_versions.yml +72 -0
  9. data/lib/trmnlp/api_client.rb +99 -0
  10. data/lib/trmnlp/app.rb +132 -0
  11. data/lib/trmnlp/browser_pool.rb +82 -0
  12. data/lib/trmnlp/cli.rb +81 -0
  13. data/lib/trmnlp/commands/base.rb +63 -0
  14. data/lib/trmnlp/commands/build.rb +26 -0
  15. data/lib/trmnlp/commands/clone.rb +35 -0
  16. data/lib/trmnlp/commands/init.rb +59 -0
  17. data/lib/trmnlp/commands/lint.rb +42 -0
  18. data/lib/trmnlp/commands/list.rb +40 -0
  19. data/lib/trmnlp/commands/login.rb +42 -0
  20. data/lib/trmnlp/commands/pull.rb +51 -0
  21. data/lib/trmnlp/commands/push.rb +77 -0
  22. data/lib/trmnlp/commands/serve.rb +54 -0
  23. data/lib/trmnlp/commands.rb +3 -0
  24. data/lib/trmnlp/config/app.rb +50 -0
  25. data/lib/trmnlp/config/plugin.rb +117 -0
  26. data/lib/trmnlp/config/project.rb +106 -0
  27. data/lib/trmnlp/config.rb +17 -0
  28. data/lib/trmnlp/context.rb +35 -0
  29. data/lib/trmnlp/errors.rb +15 -0
  30. data/lib/trmnlp/form_field.rb +42 -0
  31. data/lib/trmnlp/framework_version.rb +69 -0
  32. data/lib/trmnlp/image_quantizer.rb +58 -0
  33. data/lib/trmnlp/lint/check.rb +31 -0
  34. data/lib/trmnlp/lint/checks/custom_fields_used.rb +32 -0
  35. data/lib/trmnlp/lint/checks/form_fields_valid.rb +20 -0
  36. data/lib/trmnlp/lint/checks/highcharts_animations_disabled.rb +23 -0
  37. data/lib/trmnlp/lint/checks/highcharts_elements_unique.rb +24 -0
  38. data/lib/trmnlp/lint/checks/image_links_reachable.rb +53 -0
  39. data/lib/trmnlp/lint/checks/layouts_have_content.rb +24 -0
  40. data/lib/trmnlp/lint/checks/limited_inline_styles.rb +26 -0
  41. data/lib/trmnlp/lint/checks/no_async_functions.rb +18 -0
  42. data/lib/trmnlp/lint/checks/no_opacity.rb +19 -0
  43. data/lib/trmnlp/lint/checks/no_size_classes.rb +19 -0
  44. data/lib/trmnlp/lint/checks/title_casing.rb +20 -0
  45. data/lib/trmnlp/lint/checks/title_length.rb +18 -0
  46. data/lib/trmnlp/lint/checks/waits_for_dom_load.rb +23 -0
  47. data/lib/trmnlp/lint/source.rb +42 -0
  48. data/lib/trmnlp/lint.rb +39 -0
  49. data/lib/trmnlp/paths.rb +78 -0
  50. data/lib/trmnlp/poller.rb +105 -0
  51. data/lib/trmnlp/renderer.rb +87 -0
  52. data/lib/trmnlp/reporter.rb +28 -0
  53. data/lib/trmnlp/screen.rb +16 -0
  54. data/lib/trmnlp/screen_generator.rb +34 -0
  55. data/lib/trmnlp/screenshot.rb +96 -0
  56. data/lib/trmnlp/transform_backend/http.rb +107 -0
  57. data/lib/trmnlp/transform_backend/subprocess.rb +130 -0
  58. data/lib/trmnlp/transform_backend/wrapper.rb +113 -0
  59. data/lib/trmnlp/transform_client.rb +47 -0
  60. data/lib/trmnlp/transform_pipeline.rb +65 -0
  61. data/lib/trmnlp/user_data_assembler.rb +96 -0
  62. data/lib/trmnlp/version.rb +5 -0
  63. data/lib/trmnlp/watcher.rb +60 -0
  64. data/lib/trmnlp.rb +10 -0
  65. data/templates/init/.trmnlp.yml +14 -0
  66. data/templates/init/bin/trmnlp +44 -0
  67. data/templates/init/src/full.liquid +1 -0
  68. data/templates/init/src/half_horizontal.liquid +1 -0
  69. data/templates/init/src/half_vertical.liquid +1 -0
  70. data/templates/init/src/quadrant.liquid +1 -0
  71. data/templates/init/src/settings.yml +16 -0
  72. data/templates/init/src/shared.liquid +1 -0
  73. data/templates/init/src/transform.py.example +14 -0
  74. data/trmnl_preview.gemspec +68 -0
  75. data/web/public/highlight/highlight.min.js +315 -0
  76. data/web/public/highlight/styles/atom-one-dark.min.css +1 -0
  77. data/web/public/index.css +119 -0
  78. data/web/public/index.js +96 -0
  79. data/web/public/trmnl-picker.js +656 -0
  80. data/web/views/index.erb +73 -0
  81. data/web/views/render_html.erb +25 -0
  82. metadata +324 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d37a034edabdffdea4cfd43b0a768f43955d0d1d41d8b7d5d49a8c30de676525
4
- data.tar.gz: cc6e4d027d53e52e4c05b1a1f35ddafbbf136fa3467c5b1015d5813397935b49
3
+ metadata.gz: bf11a528033d7882be345a627e2a480f1cdf95a5f9cef22d01f51f5b46012635
4
+ data.tar.gz: 973c3dc1b96f1d7a81ffa8e9fc82f403285d9449463d210684732a43028509d8
5
5
  SHA512:
6
- metadata.gz: c55ca2009766d890fbfc851752281d587ee4f76d48df50eb144e4aff113f8ae7255c1003a9519f2182151b1dd8294edb566462306c5d7777661ef60ba0bd831e
7
- data.tar.gz: 5060f91212a819e479a12ddf72d44e970d098781abdacedd994a1716bb2642201dc5c753b09ee1da3c69de2b3a6acf191cd847967c6ae412f53f08042eca1557
6
+ metadata.gz: 39963f376ff877a4170ce57564876e5cb4bd6eacc23de4b1792a6cba6cd01f4252579fe19c8d678f21144f37e9ba050ed03866b38a3f7aaa9a67521aa324fd04
7
+ data.tar.gz: 835b494bb56d29e9e4b607a482ae05466b08aa527ff38c00fae2a98e87524bbcbb6db78c9abd5dc8288c3b36692008107a1c9bbbdd298c36ddf1d435cd0d2e65
data/CHANGELOG.md ADDED
@@ -0,0 +1,192 @@
1
+
2
+ # Changelog
3
+
4
+ ## 0.8.0
5
+
6
+ ### Housekeeping
7
+
8
+ - Upgraded the development, CI, and Docker baseline to Ruby 4.0.4
9
+ - Replaced the faye-websocket live reload with server-sent events, removing the eventmachine dependency
10
+ - Upgraded `filewatcher` to 3.x for Ruby 4.0 support
11
+ - Upgraded `mini_magick` to 5.x (ImageMagick 7 only)
12
+ - Upgraded `rubyzip` to 3.x
13
+ - Upgraded `puma` to 8.x
14
+ - Upgraded `oj` and `selenium-webdriver` to their latest releases
15
+ - Upgraded `trmnl-liquid` to 0.7 and `xdg` to 10
16
+ - Added the `cgi` gem, removed from Ruby's standard library in 4.0
17
+ - Dropped the redundant `pathname` gem dependency; Ruby provides `Pathname` built in
18
+ - Added `.rspec` configuration and a `Rakefile`
19
+
20
+ ### Refactor
21
+
22
+ - Refactored screen generation into focused objects: `Screen`, `Screenshot`, `Renderer`, `ImageQuantizer`, `BrowserPool`, `Reporter`, `Watcher`, `Poller`, `UserDataAssembler`
23
+ - Added support for `text/html` and `text/plain` polling responses with JSON body sniffing (#81)
24
+ - Fixed Liquid conditionals (`{% if %}...{% endif %}`) spanning `polling_headers` values (#79)
25
+ - Fixed multi-select custom_fields being coerced into JSON strings — arrays now preserved (#80)
26
+ - Fixed `trmnl.device.{width,height}` in user-data so they reflect the picker's selected model (#94)
27
+ - Fixed `Permission denied` from `trmnlp clone` on Linux when overwriting template files (#83)
28
+ - Fixed deprecated `convert` warning by switching to mini_magick's `Magick` tool (#89)
29
+
30
+ ### Serverless Transforms
31
+
32
+ - Added `transform_runtime:` config in `.trmnlp.yml` — serverless transforms are enabled by default and run whenever a `src/transform.*` file is present; set to `disabled` to turn them off
33
+ - Added `serverless_daemon_url:` override for pointing at a remote transform daemon (production-fidelity testing, shared team daemons)
34
+ - Added `serverless_language:` override (`python`, `ruby`, `php`, `node`)
35
+ - Added detection of `src/transform.{py,rb,php,js}` with language inferred from extension
36
+ - Added `TRMNLP::TransformClient` strategy host that selects `TransformBackend::Subprocess` (default) or `TransformBackend::Http` (when `serverless_daemon_url` is set) via `.from_config`
37
+ - Added `TRMNLP::TransformBackend::Subprocess` — local subprocess execution mirroring the hosted serverless wrapper contract verbatim, output flows back via a per-execution tempfile
38
+ - Added `python3`, `nodejs`, and `php-cli` to the main `Dockerfile`'s runtime stage alongside the existing `ruby` so all four supported transform languages work out of the box — no sidecar required
39
+ - Added transform-error surfacing in the preview UI when execution fails
40
+ - Added filewatcher re-poll when transform source changes (hot reload)
41
+ - Added `examples/hn-stories/` — a complete worked example fetching Hacker News top stories and rendering across all four sizes
42
+
43
+ ### Framework Picker
44
+
45
+ - Added `framework_version:` plugin setting in `src/settings.yml` (defaults to `latest`, supports pinning to any released version) — round-trips through `trmnlp push`/`pull` alongside the hosted plugin archive format
46
+ - Added `framework_asset_host:` override in `.trmnlp.yml` for offline / mirrored development
47
+ - Added `TRMNLP::FrameworkVersion` mirroring the hosted framework versioning
48
+ - Added `rake framework:sync` to refresh `db/data/framework_versions.yml` from a local design-system checkout
49
+ - Updated `render_html.erb` to derive CSS/JS URLs from the resolved framework version
50
+
51
+ ### FormField & Init Template
52
+
53
+ - Added FormField schema vendored from the hosted service (`db/data/form_fields.yml`) covering the full field-type allowlist
54
+ - Refreshed the `trmnlp init` template to scaffold `framework_version` and transform configuration, including a `transform.py.example`
55
+ - Fixed non-portable `/bin/bash` shebang in the generated `bin/trmnlp` (#78)
56
+
57
+ ## 0.7.0
58
+
59
+ - Switch from Puppeteer + CDP to Selenium + WebDriver BiDi (@SorceressLyra)
60
+
61
+ ## 0.6.1
62
+
63
+ - Update trmnl-liquid to 0.4.0
64
+
65
+ ## 0.6.0
66
+
67
+ - Drop trmnl-component in lieu of plain iframe
68
+ - Add [trmnl-picker](https://github.com/usetrmnl/trmnl-picker) to support new TRMNL and BYOD screens
69
+ - Fix mashup layout previews
70
+
71
+ ## 0.5.10
72
+
73
+ - Fix interpolation of multi-line polling URLs with custom fields
74
+
75
+ ## 0.5.9
76
+
77
+ - Add `pathname` dependency
78
+
79
+ ## 0.5.8
80
+
81
+ - Improve Docker commands in `bin/trmnlp` (@jrand0m, @jbarreiros)
82
+
83
+ ## 0.5.7
84
+
85
+ - Use the `trmnl-liquid` gem so tags and filters stay up-to-date with the hosted offering
86
+
87
+ ## 0.5.6
88
+
89
+ - Fixed bug that left blank plugins on server after upload failed
90
+ - Fixed bug creating upload.zip after previous upload had failed
91
+ - Added support to read API key fromk `TRMNL_API_KEY` environment variable (@andi4000)
92
+ - Fixed `init` command in Docker container (@jbarreiros)
93
+ - Automatically remove ephemeral Docker container after exit (@andi4000)
94
+
95
+ ## 0.5.5
96
+
97
+ - Added dark mode (@stephenyeargin)
98
+ - Added override for `polling_url` in project config (@heroheman)
99
+ - Reworked `bin/dev` into more generic `bin/trmnlp`
100
+ - Fixed pull, push, and clone commands on Windows (@eugenio)
101
+
102
+ ## 0.5.4
103
+
104
+ - Added `shared.liquid` file to template (@mariovisic)
105
+ - Stringified custom field values to match production (@mariovisic)
106
+ - Optimized image generation (@sd416)
107
+ - Fixed preview from growing when JSON data becomes too wide (@stephenyeargin)
108
+
109
+ ## 0.5.3
110
+
111
+ - Added support for [reusable markup](https://docs.trmnl.com/go/reusing-markup) in `shared.liquid`
112
+ - Replaced custom case images with [\<trmnl-frame\> component](https://github.com/usetrmnl/trmnl-component)
113
+ - Updated custom Liquid filters
114
+ - Added API key validation during `trmnlp login`
115
+
116
+ ## 0.5.2
117
+
118
+ - Added `time_zone` project config option, which is injected into `trmnl.user` variables
119
+ - Fixed time zone to always be UTC, matching trmnl.com servers (#38)
120
+
121
+ ## 0.5.1
122
+
123
+ - Fixed `trmnl init`
124
+
125
+ ## 0.5.0
126
+
127
+ - Added `trmlnp init` command
128
+ - Added `trmnlp clone` command
129
+ - Improved `trmnlp push` to create remote plugin on first publish
130
+ - Changed syntax of `trmnlp push` and `trmnlp pull` commands
131
+ - Added `oj` gem for JSON parsing (#32)
132
+
133
+ ## 0.4.0
134
+
135
+ ### Plugin Migration Strategy
136
+
137
+ The plugin directory structure has changed to better align with the [plugin archive format](https://help.trmnl.com/en/articles/10542599-importing-and-exporting-private-plugins#h_581fb988f0).
138
+
139
+ Here is a migration strategy for existing plugin repositories:
140
+
141
+ 1. Create `.trmnlp.yml` and bring over preview settings from `config.toml` - [see README](README.md)
142
+ 2. Rename directory `views/` to `src/`
143
+ 3. Create `src/settings.yml` and bring over plugin settings from `config.toml` - [see TRMNL docs](https://help.trmnl.com/en/articles/10542599-importing-and-exporting-private-plugins#h_581fb988f0)
144
+ 4. Delete `config.toml`
145
+
146
+ ### Changes
147
+
148
+ - Change plugin directory structure (see README for details)
149
+ - Add `login`, `push`, and `pull` commands
150
+ - Bring up-to-date with latest private plugin features:
151
+ - Add `static` strategy
152
+ - Add polling features: multiple URLs, new verbs, and request body
153
+ - Add settings `dark_mode`, `no_screen_padding`, `custom_fields`
154
+ - Add interpolation of custom fields in `polling\_\*` options
155
+ - Add `{{ trmnl }}` variables
156
+ - Add `watch` config
157
+ - Add interpolation of environment variables in `.trmnlp.yml` via `{{ env }}`
158
+ - Add auto-reload when `.trmnlp.yml` or `settings.yml` changes
159
+ - Add variable display
160
+ - Fix crash when #poll_data fails (#12)
161
+ - Fix git runtime error in Docker container (#12)
162
+
163
+
164
+
165
+ ## 0.3.2
166
+
167
+ - Add bitmap rendering
168
+ - Add TRMNL's [custom plugin filters](https://help.trmnl.com/en/articles/10347358-custom-plugin-filters)
169
+ - Add support for user-supplied custom filters
170
+
171
+ ## 0.3.1
172
+
173
+ - Add live render
174
+
175
+ ## 0.3.0
176
+
177
+ - Add poll button
178
+ - Add case image overlays
179
+ - Add `trmnlp build` command
180
+ - Add support for `url` pointing to a local JSON data file
181
+
182
+ ## 0.2.0
183
+
184
+ - Add "commands" concept to `trmnlp` executable
185
+ - `trmnlp serve` improvements
186
+ - Add argument for plugin directory
187
+ - Add options `-b` and `-p` for host bind and port, respectively
188
+ - Add Dockerfile
189
+
190
+ ## 0.1.2
191
+
192
+ - Initial working release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Rockwell Schrock
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,319 @@
1
+ # trmnlp
2
+
3
+ [![CI](https://github.com/usetrmnl/trmnlp/actions/workflows/ci.yaml/badge.svg)](https://github.com/usetrmnl/trmnlp/actions/workflows/ci.yaml)
4
+ [![Gem Version](https://img.shields.io/gem/v/trmnl_preview)](https://rubygems.org/gems/trmnl_preview)
5
+
6
+ A basic self-hosted web server to ease the development and sharing of [TRMNL](https://trmnl.com/) plugins.
7
+
8
+ [Liquid](https://shopify.github.io/liquid/) templates are rendered leveraging the [TRMNL Design System](https://trmnl.com/framework). They may be generated as HTML (faster, and a good approximation of the final result) or as PNG images (slower, but more accurate).
9
+
10
+ The server watches the filesystem for changes to the Liquid templates, seamlessly updating the preview without the need to refresh.
11
+
12
+ ![Screenshot](docs/preview.png)
13
+
14
+ ## Quick Start
15
+
16
+ ```sh
17
+ gem install trmnl_preview # install
18
+ trmnlp init my_plugin # scaffold a project
19
+ cd my_plugin
20
+ trmnlp serve # preview at http://localhost:4567
21
+ ```
22
+
23
+ No Ruby on hand? Run it through Docker instead — see [Installing via Docker](#installing-via-docker).
24
+
25
+ ## Project Structure
26
+
27
+ This is the structure of a plugin project:
28
+
29
+ ```
30
+ .
31
+ ├── .trmnlp.yml
32
+ ├── bin
33
+ │ └── trmnlp
34
+ └── src
35
+ ├── full.liquid
36
+ ├── half_horizontal.liquid
37
+ ├── half_vertical.liquid
38
+ ├── quadrant.liquid
39
+ ├── shared.liquid
40
+ └── settings.yml
41
+ ```
42
+
43
+ | File | Purpose |
44
+ |---|---|
45
+ | `.trmnlp.yml` | Local dev-server config — not uploaded to TRMNL |
46
+ | `src/full.liquid` | Markup for the full screen |
47
+ | `src/half_horizontal.liquid` | Top or bottom half of a stacked mashup |
48
+ | `src/half_vertical.liquid` | Left or right half of a side-by-side mashup |
49
+ | `src/quadrant.liquid` | One quarter of a 2x2 mashup |
50
+ | `src/shared.liquid` | Reusable markup included by the other templates |
51
+ | `src/settings.yml` | Plugin configuration — uploaded to TRMNL |
52
+
53
+ ## Creating a New Plugin
54
+
55
+ You can start building a plugin locally, then `push` it to the TRMNL server for display on your device.
56
+
57
+ ```sh
58
+ trmnlp init [my_plugin] # generate
59
+ cd [my_plugin]
60
+ trmnlp serve # develop locally
61
+ trmnlp login # authenticate
62
+ trmnlp push # upload
63
+ ```
64
+
65
+ ## Modifying an Existing Plugin
66
+
67
+ If you have built a plugin with the web-based editor, you can `clone` it, work on it locally, and `push` changes back to the server.
68
+
69
+ ```sh
70
+ trmnlp login # authenticate
71
+ trmnlp clone [my_plugin] [id] # first download
72
+ cd [my_plugin]
73
+ trmnlp serve # develop locally
74
+ trmnlp push # upload
75
+ ```
76
+
77
+ ## Commands
78
+
79
+ | Command | Description |
80
+ |---|---|
81
+ | `trmnlp init NAME` | Start a new plugin project |
82
+ | `trmnlp serve` | Start a local dev server |
83
+ | `trmnlp build` | Generate static HTML files |
84
+ | `trmnlp lint` | Check plugin code against TRMNL best practices |
85
+ | `trmnlp login` | Authenticate with TRMNL server |
86
+ | `trmnlp list` | List private plugins from TRMNL server |
87
+ | `trmnlp clone NAME ID` | Copy a plugin project from TRMNL server |
88
+ | `trmnlp pull` | Download latest plugin settings from TRMNL server |
89
+ | `trmnlp push` | Upload latest plugin settings to TRMNL server |
90
+ | `trmnlp version` | Show version |
91
+
92
+ `trmnlp lint` exits non-zero when it finds issues, so you can gate CI on it. Run `trmnlp help` for all flags.
93
+
94
+ ## Authentication
95
+
96
+ The `trmnlp login` command saves your API key to `~/.config/trmnlp/config.yml`.
97
+
98
+ If an environment variable is more convenient (for example in a CI/CD pipeline), you can set `$TRMNL_API_KEY` instead.
99
+
100
+ ## Running trmnlp
101
+
102
+ 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.
103
+
104
+ You can modify the `bin/trmnlp` script to set up environment variables (plugin secrets, etc.) before running the server.
105
+
106
+ **Gem or Docker?** Install the gem if you already have Ruby >= 3.4 — it has the fastest startup. Use Docker for zero local setup.
107
+
108
+ ### Installing via RubyGems
109
+
110
+ Prerequisites:
111
+
112
+ - Ruby >= 3.4
113
+ - For PNG rendering (optional):
114
+ - Firefox
115
+ - ImageMagick
116
+
117
+ ```sh
118
+ gem install trmnl_preview
119
+ trmnlp serve
120
+ ```
121
+
122
+ ### Installing via Docker
123
+
124
+ ```sh
125
+ docker run \
126
+ --publish 4567:4567 \
127
+ --volume "$(pwd):/plugin" \
128
+ trmnl/trmnlp serve
129
+ ```
130
+
131
+ Inside a container, `serve` binds to `0.0.0.0` automatically (it detects `/.dockerenv`) so the preview is reachable from your host browser. Outside Docker it binds to `127.0.0.1`.
132
+
133
+ Swap `serve` for any other command (`lint`, `login`, `clone`, etc.) to run it in a one-off container.
134
+
135
+ #### Interactive Mode
136
+
137
+ For running multiple commands (login, clone, serve), you can start an interactive shell:
138
+
139
+ ```sh
140
+ docker run -it \
141
+ --publish 4567:4567 \
142
+ --volume "$HOME/.config/trmnlp:/root/.config/trmnlp" \
143
+ --volume "$(pwd):/plugin" \
144
+ --entrypoint /bin/bash \
145
+ trmnl/trmnlp
146
+ ```
147
+
148
+ Then run commands inside the container:
149
+
150
+ ```sh
151
+ trmnlp login
152
+ trmnlp clone my_plugin 12345
153
+ cd my_plugin
154
+ trmnlp serve
155
+ ```
156
+
157
+ The config volume (`$HOME/.config/trmnlp`) persists your API key between sessions.
158
+
159
+ #### Docker Compose
160
+
161
+ For a checked-in config — like [`examples/hn-stories/`](examples/hn-stories/) uses — a minimal `docker-compose.yml`:
162
+
163
+ ```yaml
164
+ services:
165
+ trmnlp:
166
+ image: trmnl/trmnlp
167
+ command: ["serve"]
168
+ ports:
169
+ - "4567:4567"
170
+ volumes:
171
+ - .:/plugin
172
+ ```
173
+
174
+ Then `docker compose up`.
175
+
176
+ #### Building Locally
177
+
178
+ To build the Docker image from source:
179
+
180
+ ```sh
181
+ git clone https://github.com/usetrmnl/trmnlp.git
182
+ cd trmnlp
183
+ docker build -t trmnlp .
184
+ ```
185
+
186
+ ## `.trmnlp.yml` Reference - Project Config
187
+
188
+ The `.trmnlp.yml` file lives in the root of the plugin project, and is for configuring the local dev server.
189
+
190
+ System environment variables are made available in the `{{ env }}` Liquid varible in this file only. This can be used to safely
191
+ supply plugin secrets, like API keys.
192
+
193
+ All fields are optional.
194
+
195
+ ```yaml
196
+ ---
197
+ # auto-reload when files change (`watch: false` to disable)
198
+ watch:
199
+ - src
200
+ - .trmnlp.yml
201
+
202
+ # values of custom fields (defined in src/settings.yml)
203
+ custom_fields:
204
+ station: "{{ env.ICAO }}" # interpolate $IACO environment variable
205
+
206
+ # Time zone IANA identifier to inject into trmnl.user; see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
207
+ time_zone: America/New_York
208
+
209
+ # Serverless transforms run automatically when a src/transform.*
210
+ # file is present. Set to 'disabled' to turn off.
211
+ transform_runtime: enabled
212
+
213
+ # Optional remote transform daemon URL — when set, transforms POST
214
+ # here instead of running locally. Useful for production-fidelity
215
+ # testing against a real microVM daemon.
216
+ # serverless_daemon_url: https://transforms.your-team.example
217
+
218
+ # Optional explicit language for src/transform.* (otherwise inferred from extension)
219
+ # serverless_language: python
220
+
221
+ # override variables
222
+ variables:
223
+ trmnl:
224
+ user:
225
+ name: Peter Quill
226
+ plugin_settings:
227
+ instance_name: Kevin Bacon Facts
228
+
229
+ ```
230
+
231
+ ## Serverless Transforms
232
+
233
+ `trmnlp` can run a transform script (`python`, `ruby`, `php`, or `node`) against the polled API response before handing data to your Liquid templates — matching the hosted plugin service's behavior.
234
+
235
+ Drop a file at `src/transform.{py,rb,php,js}` and define a `run(input)` function — transforms are enabled by default, so it runs automatically. To turn them off, set `transform_runtime: disabled` in `.trmnlp.yml`.
236
+
237
+ > **Heads up:** because transforms run by default, a plugin you `clone` or `pull` from somewhere else will execute its `src/transform.*` code on your machine the first time you preview it — there is no opt-in prompt. Review a third-party plugin's transform script before serving it, or set `transform_runtime: disabled`.
238
+
239
+ The transform receives the polled response on stdin as JSON; whatever `run(input)` returns becomes the new merge data.
240
+
241
+ Example `src/transform.py`:
242
+
243
+ ```python
244
+ def run(input):
245
+ return {"items": [x["title"] for x in input["data"]]}
246
+ ```
247
+
248
+ The trmnlp image bundles `python3`, `node`, `php`, and `ruby` — no sidecar daemon required.
249
+
250
+ ### Language detection
251
+
252
+ The transform language comes from the **file extension**:
253
+
254
+ | File | Language |
255
+ |-------------------|----------|
256
+ | `src/transform.py` | `python` |
257
+ | `src/transform.rb` | `ruby` |
258
+ | `src/transform.js` | `node` |
259
+ | `src/transform.php` | `php` |
260
+
261
+ `trmnlp push` uploads the file under its own name, and the hosted service records `serverless_language` from the extension automatically — you don't need to set it by hand. `trmnlp pull` / `trmnlp clone` bring the transform file back under the same name.
262
+
263
+ ### Pointing at a remote daemon
264
+
265
+ For production-fidelity testing against a real microVM daemon, set `serverless_daemon_url:` in `.trmnlp.yml`:
266
+
267
+ ```yaml
268
+ transform_runtime: enabled
269
+ serverless_daemon_url: https://transforms.your-team.example
270
+ ```
271
+
272
+ Provide the daemon's bearer token via `$TRMNL_SERVERLESS_DAEMON_API_KEY` (env-first, mirroring how `$TRMNL_API_KEY` works for trmnl.com auth):
273
+
274
+ ```sh
275
+ export TRMNL_SERVERLESS_DAEMON_API_KEY=...
276
+ trmnlp serve
277
+ ```
278
+
279
+ Or commit a per-project value to `.trmnlp.yml` as `serverless_daemon_api_key:` — though the env var is preferred to keep the secret out of version control.
280
+
281
+ A complete worked example lives at [`examples/hn-stories/`](examples/hn-stories/) — a polling plugin that fetches the Hacker News top-stories list, enriches each story via additional HTTPS calls from inside the transform, and renders the result with TRMNL design-system markup across all four sizes. `cd examples/hn-stories && docker compose up` and you're running it.
282
+
283
+ ## `src/settings.yml` Reference (Plugin Config)
284
+
285
+ The `settings.yml` file is part of the plugin definition, and is uploaded and downloaded by `trmnlp push` / `pull`.
286
+
287
+ `framework_version:` pins the [TRMNL Design System](https://trmnl.com/framework) version this plugin renders against — `latest` (the default) tracks the newest release, or set a specific version for reproducibility. It lives here rather than in `.trmnlp.yml` so the value round-trips with the hosted plugin service.
288
+
289
+ See [TRMNL documentation](https://help.trmnl.com/en/articles/10542599-importing-and-exporting-private-plugins#h_581fb988f0) for details on this file's contents.
290
+
291
+
292
+ ## Development
293
+
294
+ To run trmnlp from a checkout of this repo — handy for trying unreleased changes or contributing:
295
+
296
+ ```sh
297
+ git clone https://github.com/usetrmnl/trmnlp.git
298
+ cd trmnlp
299
+ bundle install
300
+ bundle exec bin/trmnlp serve
301
+ ```
302
+
303
+ The repo pins its Ruby version in `.ruby-version` — a version manager will pick it up when you `cd` in. This `bin/trmnlp` runs the CLI straight from `lib/`; it's a different script from the gem-or-Docker `bin/trmnlp` that `trmnlp init` scaffolds into a plugin project.
304
+
305
+ ## Tests
306
+
307
+ To test, run:
308
+
309
+ ```sh
310
+ bin/rake
311
+ ```
312
+
313
+ ## Contributing
314
+
315
+ Bug reports and pull requests are welcome on GitHub at https://github.com/usetrmnl/trmnlp.
316
+
317
+ ## License
318
+
319
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/bin/rake ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
12
+
13
+ bundle_binstub = File.expand_path('bundle', __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?('This file was generated by Bundler')
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require 'rubygems'
25
+ require 'bundler/setup'
26
+
27
+ load Gem.bin_path('rake', 'rake')
data/bin/trmnlp CHANGED
@@ -1,19 +1,18 @@
1
- #!/usr/bin/env ruby
1
+ #! /usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- warn <<~MESSAGE
5
- trmnlp requires Ruby 4.0 or newer.
4
+ require 'thor'
6
5
 
7
- You are running Ruby #{RUBY_VERSION}. RubyGems installed this notice
8
- instead of trmnlp, because the current release cannot run on your Ruby.
6
+ require_relative '../lib/trmnlp/cli'
9
7
 
10
- To use trmnlp, either:
8
+ # Force UTC to match trmnl.com server time zone
9
+ ENV['TZ'] = 'UTC'
11
10
 
12
- * Upgrade to Ruby 4.0 or newer, then run:
13
- gem install trmnl_preview
14
-
15
- * Or run it through Docker, no Ruby needed:
16
- https://github.com/usetrmnl/trmnlp#installing-via-docker
17
- MESSAGE
18
-
19
- exit 1
11
+ begin
12
+ TRMNLP::CLI.start
13
+ rescue TRMNLP::Error => e
14
+ puts "Error: #{e.message}"
15
+ exit 1
16
+ rescue Interrupt
17
+ exit 1
18
+ end
@@ -0,0 +1,24 @@
1
+ # Form-field schema vendored from the TRMNL hosted service.
2
+ #
3
+ # Unlike framework_versions.yml there is no upstream YAML to sync from —
4
+ # the canonical schema lives in the hosted service's Ruby code. Refresh
5
+ # this file by hand when that schema changes.
6
+ #
7
+ # author_bio is included although the hosted service validates it on a
8
+ # separate branch (so it is absent from the hosted FIELDS allowlist) —
9
+ # it is still a real field type a plugin may declare.
10
+ required_keys:
11
+ - keyname
12
+ - field_type
13
+ - name
14
+ field_types:
15
+ - text
16
+ - string
17
+ - number
18
+ - password
19
+ - boolean
20
+ - xhrString
21
+ - xhrSelect
22
+ - xhrFunction
23
+ - select
24
+ - author_bio