trmnl_preview 0.7.1 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +54 -1
- data/README.md +180 -5
- data/bin/rake +6 -6
- data/bin/trmnlp +1 -0
- data/db/data/form_fields.yml +24 -0
- data/db/data/framework_versions.yml +72 -0
- data/lib/trmnlp/api_client.rb +41 -28
- data/lib/trmnlp/app.rb +73 -44
- data/lib/trmnlp/browser_pool.rb +82 -0
- data/lib/trmnlp/cli.rb +24 -11
- data/lib/trmnlp/commands/base.rb +33 -10
- data/lib/trmnlp/commands/build.rb +13 -8
- data/lib/trmnlp/commands/clone.rb +12 -7
- data/lib/trmnlp/commands/init.rb +17 -13
- data/lib/trmnlp/commands/lint.rb +42 -0
- data/lib/trmnlp/commands/list.rb +40 -0
- data/lib/trmnlp/commands/login.rb +28 -13
- data/lib/trmnlp/commands/pull.rb +14 -6
- data/lib/trmnlp/commands/push.rb +29 -19
- data/lib/trmnlp/commands/serve.rb +32 -3
- data/lib/trmnlp/commands.rb +3 -1
- data/lib/trmnlp/config/app.rb +6 -3
- data/lib/trmnlp/config/plugin.rb +56 -14
- data/lib/trmnlp/config/project.rb +59 -7
- data/lib/trmnlp/config.rb +3 -1
- data/lib/trmnlp/context.rb +21 -224
- data/lib/trmnlp/errors.rb +15 -0
- data/lib/trmnlp/form_field.rb +42 -0
- data/lib/trmnlp/framework_version.rb +69 -0
- data/lib/trmnlp/image_quantizer.rb +58 -0
- data/lib/trmnlp/lint/check.rb +31 -0
- data/lib/trmnlp/lint/checks/custom_fields_used.rb +32 -0
- data/lib/trmnlp/lint/checks/form_fields_valid.rb +20 -0
- data/lib/trmnlp/lint/checks/highcharts_animations_disabled.rb +23 -0
- data/lib/trmnlp/lint/checks/highcharts_elements_unique.rb +24 -0
- data/lib/trmnlp/lint/checks/image_links_reachable.rb +53 -0
- data/lib/trmnlp/lint/checks/layouts_have_content.rb +24 -0
- data/lib/trmnlp/lint/checks/limited_inline_styles.rb +26 -0
- data/lib/trmnlp/lint/checks/no_async_functions.rb +18 -0
- data/lib/trmnlp/lint/checks/no_opacity.rb +19 -0
- data/lib/trmnlp/lint/checks/no_size_classes.rb +19 -0
- data/lib/trmnlp/lint/checks/title_casing.rb +20 -0
- data/lib/trmnlp/lint/checks/title_length.rb +18 -0
- data/lib/trmnlp/lint/checks/waits_for_dom_load.rb +23 -0
- data/lib/trmnlp/lint/source.rb +42 -0
- data/lib/trmnlp/lint.rb +39 -0
- data/lib/trmnlp/paths.rb +28 -8
- data/lib/trmnlp/poller.rb +105 -0
- data/lib/trmnlp/renderer.rb +87 -0
- data/lib/trmnlp/reporter.rb +28 -0
- data/lib/trmnlp/screen.rb +16 -0
- data/lib/trmnlp/screen_generator.rb +11 -217
- data/lib/trmnlp/screenshot.rb +96 -0
- data/lib/trmnlp/transform_backend/http.rb +107 -0
- data/lib/trmnlp/transform_backend/subprocess.rb +130 -0
- data/lib/trmnlp/transform_backend/wrapper.rb +113 -0
- data/lib/trmnlp/transform_client.rb +47 -0
- data/lib/trmnlp/transform_pipeline.rb +65 -0
- data/lib/trmnlp/user_data_assembler.rb +96 -0
- data/lib/trmnlp/version.rb +1 -1
- data/lib/trmnlp/watcher.rb +60 -0
- data/lib/trmnlp.rb +6 -10
- data/templates/init/bin/trmnlp +1 -1
- data/templates/init/src/settings.yml +1 -0
- data/templates/init/src/transform.py.example +14 -0
- data/trmnl_preview.gemspec +34 -34
- data/web/public/index.css +6 -0
- data/web/public/index.js +31 -18
- data/web/views/index.erb +6 -1
- data/web/views/render_html.erb +4 -2
- metadata +81 -56
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bf11a528033d7882be345a627e2a480f1cdf95a5f9cef22d01f51f5b46012635
|
|
4
|
+
data.tar.gz: 973c3dc1b96f1d7a81ffa8e9fc82f403285d9449463d210684732a43028509d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 39963f376ff877a4170ce57564876e5cb4bd6eacc23de4b1792a6cba6cd01f4252579fe19c8d678f21144f37e9ba050ed03866b38a3f7aaa9a67521aa324fd04
|
|
7
|
+
data.tar.gz: 835b494bb56d29e9e4b607a482ae05466b08aa527ff38c00fae2a98e87524bbcbb6db78c9abd5dc8288c3b36692008107a1c9bbbdd298c36ddf1d435cd0d2e65
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
|
|
2
2
|
# Changelog
|
|
3
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
|
+
|
|
4
57
|
## 0.7.0
|
|
5
58
|
|
|
6
59
|
- Switch from Puppeteer + CDP to Selenium + WebDriver BiDi (@SorceressLyra)
|
|
@@ -29,7 +82,7 @@
|
|
|
29
82
|
|
|
30
83
|
## 0.5.7
|
|
31
84
|
|
|
32
|
-
- Use the `trmnl-liquid` gem so tags and filters stay up-to-date with the
|
|
85
|
+
- Use the `trmnl-liquid` gem so tags and filters stay up-to-date with the hosted offering
|
|
33
86
|
|
|
34
87
|
## 0.5.6
|
|
35
88
|
|
data/README.md
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
# trmnlp
|
|
2
2
|
|
|
3
|
+
[](https://github.com/usetrmnl/trmnlp/actions/workflows/ci.yaml)
|
|
4
|
+
[](https://rubygems.org/gems/trmnl_preview)
|
|
5
|
+
|
|
3
6
|
A basic self-hosted web server to ease the development and sharing of [TRMNL](https://trmnl.com/) plugins.
|
|
4
7
|
|
|
5
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).
|
|
6
9
|
|
|
7
|
-
Custom Liquid filters and tags are provided by the [trmnl-liquid](https://github.com/usetrmnl/trmnl-liquid) gem.
|
|
8
|
-
|
|
9
10
|
The server watches the filesystem for changes to the Liquid templates, seamlessly updating the preview without the need to refresh.
|
|
10
11
|
|
|
11
12
|

|
|
12
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
|
+
|
|
13
25
|
## Project Structure
|
|
14
26
|
|
|
15
27
|
This is the structure of a plugin project:
|
|
@@ -18,7 +30,7 @@ This is the structure of a plugin project:
|
|
|
18
30
|
.
|
|
19
31
|
├── .trmnlp.yml
|
|
20
32
|
├── bin
|
|
21
|
-
│ └──
|
|
33
|
+
│ └── trmnlp
|
|
22
34
|
└── src
|
|
23
35
|
├── full.liquid
|
|
24
36
|
├── half_horizontal.liquid
|
|
@@ -28,6 +40,16 @@ This is the structure of a plugin project:
|
|
|
28
40
|
└── settings.yml
|
|
29
41
|
```
|
|
30
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
|
+
|
|
31
53
|
## Creating a New Plugin
|
|
32
54
|
|
|
33
55
|
You can start building a plugin locally, then `push` it to the TRMNL server for display on your device.
|
|
@@ -52,6 +74,23 @@ trmnlp serve # develop locally
|
|
|
52
74
|
trmnlp push # upload
|
|
53
75
|
```
|
|
54
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
|
+
|
|
55
94
|
## Authentication
|
|
56
95
|
|
|
57
96
|
The `trmnlp login` command saves your API key to `~/.config/trmnlp/config.yml`.
|
|
@@ -64,11 +103,13 @@ The `bin/trmnlp` script is provided as a convenience. It will use the local Ruby
|
|
|
64
103
|
|
|
65
104
|
You can modify the `bin/trmnlp` script to set up environment variables (plugin secrets, etc.) before running the server.
|
|
66
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
|
+
|
|
67
108
|
### Installing via RubyGems
|
|
68
109
|
|
|
69
110
|
Prerequisites:
|
|
70
111
|
|
|
71
|
-
- Ruby 3.
|
|
112
|
+
- Ruby >= 3.4
|
|
72
113
|
- For PNG rendering (optional):
|
|
73
114
|
- Firefox
|
|
74
115
|
- ImageMagick
|
|
@@ -87,6 +128,61 @@ docker run \
|
|
|
87
128
|
trmnl/trmnlp serve
|
|
88
129
|
```
|
|
89
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
|
+
|
|
90
186
|
## `.trmnlp.yml` Reference - Project Config
|
|
91
187
|
|
|
92
188
|
The `.trmnlp.yml` file lives in the root of the plugin project, and is for configuring the local dev server.
|
|
@@ -110,6 +206,18 @@ custom_fields:
|
|
|
110
206
|
# Time zone IANA identifier to inject into trmnl.user; see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
|
111
207
|
time_zone: America/New_York
|
|
112
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
|
+
|
|
113
221
|
# override variables
|
|
114
222
|
variables:
|
|
115
223
|
trmnl:
|
|
@@ -120,13 +228,80 @@ variables:
|
|
|
120
228
|
|
|
121
229
|
```
|
|
122
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
|
+
|
|
123
283
|
## `src/settings.yml` Reference (Plugin Config)
|
|
124
284
|
|
|
125
|
-
The `settings.yml` file is part of the plugin definition
|
|
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.
|
|
126
288
|
|
|
127
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.
|
|
128
290
|
|
|
129
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
|
+
|
|
130
305
|
## Tests
|
|
131
306
|
|
|
132
307
|
To test, run:
|
data/bin/rake
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
# this file is here to facilitate running it.
|
|
9
9
|
#
|
|
10
10
|
|
|
11
|
-
ENV[
|
|
11
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
|
12
12
|
|
|
13
|
-
bundle_binstub = File.expand_path(
|
|
13
|
+
bundle_binstub = File.expand_path('bundle', __dir__)
|
|
14
14
|
|
|
15
15
|
if File.file?(bundle_binstub)
|
|
16
|
-
if File.read(bundle_binstub, 300).include?(
|
|
16
|
+
if File.read(bundle_binstub, 300).include?('This file was generated by Bundler')
|
|
17
17
|
load(bundle_binstub)
|
|
18
18
|
else
|
|
19
19
|
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
|
@@ -21,7 +21,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this
|
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
require
|
|
25
|
-
require
|
|
24
|
+
require 'rubygems'
|
|
25
|
+
require 'bundler/setup'
|
|
26
26
|
|
|
27
|
-
load Gem.bin_path(
|
|
27
|
+
load Gem.bin_path('rake', 'rake')
|
data/bin/trmnlp
CHANGED
|
@@ -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
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Mirrored from the TRMNL design-system source.
|
|
2
|
+
# Refresh with `rake framework:sync` — do not edit manually.
|
|
3
|
+
latest: 3.1.1
|
|
4
|
+
versions:
|
|
5
|
+
- number: 0.0.1
|
|
6
|
+
released_at: '2024-06-22'
|
|
7
|
+
- number: 0.0.2
|
|
8
|
+
released_at: '2024-06-23'
|
|
9
|
+
- number: 0.0.3
|
|
10
|
+
released_at: '2024-07-09'
|
|
11
|
+
- number: 0.0.4
|
|
12
|
+
released_at: '2024-07-11'
|
|
13
|
+
- number: 0.0.5
|
|
14
|
+
released_at: '2024-07-14'
|
|
15
|
+
- number: 0.0.6
|
|
16
|
+
released_at: '2024-08-18'
|
|
17
|
+
- number: 0.0.7
|
|
18
|
+
released_at: '2024-09-19'
|
|
19
|
+
- number: 1.0.0
|
|
20
|
+
released_at: '2024-11-03'
|
|
21
|
+
- number: 1.0.1
|
|
22
|
+
released_at: '2025-01-21'
|
|
23
|
+
- number: 1.0.2
|
|
24
|
+
released_at: '2025-02-04'
|
|
25
|
+
- number: 1.0.3
|
|
26
|
+
released_at: '2025-02-06'
|
|
27
|
+
- number: 1.0.4
|
|
28
|
+
released_at: '2025-02-24'
|
|
29
|
+
- number: 1.0.5
|
|
30
|
+
released_at: '2025-04-23'
|
|
31
|
+
- number: 1.1.0
|
|
32
|
+
released_at: '2025-05-19'
|
|
33
|
+
- number: 1.1.1
|
|
34
|
+
released_at: '2025-05-20'
|
|
35
|
+
- number: 1.2.0
|
|
36
|
+
released_at: '2025-07-28'
|
|
37
|
+
- number: 2.0.0
|
|
38
|
+
released_at: '2025-09-14'
|
|
39
|
+
- number: 2.0.1
|
|
40
|
+
released_at: '2025-11-10'
|
|
41
|
+
- number: 2.1.0
|
|
42
|
+
released_at: '2025-12-30'
|
|
43
|
+
- number: 2.2.0
|
|
44
|
+
released_at: '2026-01-13'
|
|
45
|
+
- number: 2.2.1
|
|
46
|
+
released_at: '2026-01-16'
|
|
47
|
+
- number: 2.3.0
|
|
48
|
+
released_at: '2026-02-07'
|
|
49
|
+
- number: 2.3.1
|
|
50
|
+
released_at: '2026-02-09'
|
|
51
|
+
- number: 2.3.2
|
|
52
|
+
released_at: '2026-02-26'
|
|
53
|
+
- number: 2.3.3
|
|
54
|
+
released_at: '2026-03-02'
|
|
55
|
+
- number: 2.3.7
|
|
56
|
+
released_at: '2026-03-20'
|
|
57
|
+
- number: 3.0.0
|
|
58
|
+
released_at: '2026-04-01'
|
|
59
|
+
- number: 3.0.1
|
|
60
|
+
released_at: '2026-04-03'
|
|
61
|
+
- number: 3.0.2
|
|
62
|
+
released_at: '2026-04-03'
|
|
63
|
+
- number: 3.0.3
|
|
64
|
+
released_at: '2026-04-07'
|
|
65
|
+
- number: 3.0.4
|
|
66
|
+
released_at: '2026-04-29'
|
|
67
|
+
- number: 3.0.5
|
|
68
|
+
released_at: '2026-05-01'
|
|
69
|
+
- number: 3.1.0
|
|
70
|
+
released_at: '2026-04-29'
|
|
71
|
+
- number: 3.1.1
|
|
72
|
+
released_at: '2026-05-01'
|
data/lib/trmnlp/api_client.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'faraday'
|
|
2
4
|
require 'faraday/multipart'
|
|
3
5
|
|
|
@@ -9,20 +11,36 @@ module TRMNLP
|
|
|
9
11
|
@config = config
|
|
10
12
|
end
|
|
11
13
|
|
|
14
|
+
def get_me
|
|
15
|
+
response = conn.get('me')
|
|
16
|
+
|
|
17
|
+
raise Error, "failed to fetch user info: #{response.status} #{response.body}" unless response.status == 200
|
|
18
|
+
|
|
19
|
+
JSON.parse(response.body)['data']
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def get_plugin_settings
|
|
23
|
+
response = conn.get('plugin_settings')
|
|
24
|
+
|
|
25
|
+
raise Error, "failed to list plugin settings: #{response.status} #{response.body}" unless response.status == 200
|
|
26
|
+
|
|
27
|
+
JSON.parse(response.body)['data']
|
|
28
|
+
end
|
|
29
|
+
|
|
12
30
|
def get_plugin_setting_archive(id)
|
|
13
31
|
response = conn.get("plugin_settings/#{id}/archive")
|
|
14
32
|
|
|
15
|
-
|
|
16
|
-
temp_file = Tempfile.new(["plugin_settings_#{id}", '.zip'])
|
|
17
|
-
temp_file.binmode
|
|
18
|
-
temp_file.write(response.body)
|
|
19
|
-
temp_file.rewind
|
|
20
|
-
|
|
21
|
-
# return the temp file IO
|
|
22
|
-
temp_file
|
|
23
|
-
else
|
|
33
|
+
unless response.status == 200
|
|
24
34
|
raise Error, "failed to download plugin settings archive: #{response.status} #{response.body}"
|
|
25
35
|
end
|
|
36
|
+
|
|
37
|
+
temp_file = Tempfile.new(["plugin_settings_#{id}", '.zip'])
|
|
38
|
+
temp_file.binmode
|
|
39
|
+
temp_file.write(response.body)
|
|
40
|
+
temp_file.rewind
|
|
41
|
+
|
|
42
|
+
# return the temp file IO
|
|
43
|
+
temp_file
|
|
26
44
|
end
|
|
27
45
|
|
|
28
46
|
def post_plugin_setting_archive(id, path)
|
|
@@ -36,37 +54,33 @@ module TRMNLP
|
|
|
36
54
|
|
|
37
55
|
filepart.close
|
|
38
56
|
|
|
39
|
-
|
|
40
|
-
JSON.parse(response.body)
|
|
41
|
-
else
|
|
57
|
+
unless response.status == 200
|
|
42
58
|
raise Error, "failed to upload plugin settings archive: #{response.status} #{response.body}"
|
|
43
59
|
end
|
|
60
|
+
|
|
61
|
+
JSON.parse(response.body)
|
|
44
62
|
end
|
|
45
63
|
|
|
46
64
|
def post_plugin_setting(params)
|
|
47
|
-
response = conn.post(
|
|
65
|
+
response = conn.post('plugin_settings', params.to_json, content_type: 'application/json')
|
|
48
66
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
raise Error, "failed to create plugin setting: #{response.status} #{response.body}"
|
|
53
|
-
end
|
|
67
|
+
raise Error, "failed to create plugin setting: #{response.status} #{response.body}" unless response.status == 200
|
|
68
|
+
|
|
69
|
+
JSON.parse(response.body)
|
|
54
70
|
end
|
|
55
71
|
|
|
56
72
|
def delete_plugin_setting(id)
|
|
57
73
|
response = conn.delete("plugin_settings/#{id}")
|
|
58
74
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
raise Error, "failed to delete plugin setting: #{response.status} #{response.body}"
|
|
63
|
-
end
|
|
75
|
+
raise Error, "failed to delete plugin setting: #{response.status} #{response.body}" unless response.status == 204
|
|
76
|
+
|
|
77
|
+
true
|
|
64
78
|
end
|
|
65
79
|
|
|
66
80
|
private
|
|
67
|
-
|
|
81
|
+
|
|
68
82
|
attr_reader :config
|
|
69
|
-
|
|
83
|
+
|
|
70
84
|
def api_uri = config.app.api_uri
|
|
71
85
|
|
|
72
86
|
def conn
|
|
@@ -75,12 +89,11 @@ module TRMNLP
|
|
|
75
89
|
end
|
|
76
90
|
end
|
|
77
91
|
|
|
78
|
-
|
|
79
92
|
def headers
|
|
80
93
|
{
|
|
81
94
|
'Authorization' => "Bearer #{config.app.api_key}",
|
|
82
|
-
'User-Agent' => "trmnlp/#{VERSION}"
|
|
95
|
+
'User-Agent' => "trmnlp/#{VERSION}"
|
|
83
96
|
}
|
|
84
97
|
end
|
|
85
98
|
end
|
|
86
|
-
end
|
|
99
|
+
end
|