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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +192 -0
- data/LICENSE.txt +21 -0
- data/README.md +319 -0
- data/bin/rake +27 -0
- data/bin/trmnlp +13 -14
- data/db/data/form_fields.yml +24 -0
- data/db/data/framework_versions.yml +72 -0
- data/lib/trmnlp/api_client.rb +99 -0
- data/lib/trmnlp/app.rb +132 -0
- data/lib/trmnlp/browser_pool.rb +82 -0
- data/lib/trmnlp/cli.rb +81 -0
- data/lib/trmnlp/commands/base.rb +63 -0
- data/lib/trmnlp/commands/build.rb +26 -0
- data/lib/trmnlp/commands/clone.rb +35 -0
- data/lib/trmnlp/commands/init.rb +59 -0
- data/lib/trmnlp/commands/lint.rb +42 -0
- data/lib/trmnlp/commands/list.rb +40 -0
- data/lib/trmnlp/commands/login.rb +42 -0
- data/lib/trmnlp/commands/pull.rb +51 -0
- data/lib/trmnlp/commands/push.rb +77 -0
- data/lib/trmnlp/commands/serve.rb +54 -0
- data/lib/trmnlp/commands.rb +3 -0
- data/lib/trmnlp/config/app.rb +50 -0
- data/lib/trmnlp/config/plugin.rb +117 -0
- data/lib/trmnlp/config/project.rb +106 -0
- data/lib/trmnlp/config.rb +17 -0
- data/lib/trmnlp/context.rb +35 -0
- 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 +78 -0
- 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 +34 -0
- 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 +5 -0
- data/lib/trmnlp/watcher.rb +60 -0
- data/lib/trmnlp.rb +10 -0
- data/templates/init/.trmnlp.yml +14 -0
- data/templates/init/bin/trmnlp +44 -0
- data/templates/init/src/full.liquid +1 -0
- data/templates/init/src/half_horizontal.liquid +1 -0
- data/templates/init/src/half_vertical.liquid +1 -0
- data/templates/init/src/quadrant.liquid +1 -0
- data/templates/init/src/settings.yml +16 -0
- data/templates/init/src/shared.liquid +1 -0
- data/templates/init/src/transform.py.example +14 -0
- data/trmnl_preview.gemspec +68 -0
- data/web/public/highlight/highlight.min.js +315 -0
- data/web/public/highlight/styles/atom-one-dark.min.css +1 -0
- data/web/public/index.css +119 -0
- data/web/public/index.js +96 -0
- data/web/public/trmnl-picker.js +656 -0
- data/web/views/index.erb +73 -0
- data/web/views/render_html.erb +25 -0
- metadata +324 -15
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRMNLP
|
|
4
|
+
module TransformBackend
|
|
5
|
+
# Code wrappers shared by Subprocess and Http backends. Each method
|
|
6
|
+
# emits the canonical dispatch harness — read stdin → run user code
|
|
7
|
+
# → find run/transform/result/passthrough → serialize output — and
|
|
8
|
+
# delegates the final write to a language-appropriate `output_sink`
|
|
9
|
+
# snippet supplied by the caller. Subprocess writes to a tempfile
|
|
10
|
+
# path, Http writes to FD 3 for the production daemon to capture.
|
|
11
|
+
#
|
|
12
|
+
# Mirrors the hosted serverless runtime's code-wrapping behavior
|
|
13
|
+
# verbatim except for the configurable sink.
|
|
14
|
+
#
|
|
15
|
+
# NOTE: `output_sink` is spliced verbatim into the generated script as
|
|
16
|
+
# executable code. It MUST be trmnlp-generated (see Subprocess#sink_for
|
|
17
|
+
# and Http#sink_for) and never derived from user input or config — an
|
|
18
|
+
# attacker-influenced sink is arbitrary code execution in the transform
|
|
19
|
+
# process. Only `code` is untrusted; the sink is part of the harness.
|
|
20
|
+
module Wrapper
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
def python(code, output_sink)
|
|
24
|
+
<<~PYTHON
|
|
25
|
+
import sys, json, os
|
|
26
|
+
input = json.loads(sys.stdin.read())
|
|
27
|
+
|
|
28
|
+
#{code}
|
|
29
|
+
|
|
30
|
+
if callable(locals().get('run', None)):
|
|
31
|
+
output = run(input)
|
|
32
|
+
elif 'result' in dir():
|
|
33
|
+
output = result
|
|
34
|
+
else:
|
|
35
|
+
output = input
|
|
36
|
+
#{output_sink}
|
|
37
|
+
PYTHON
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ruby(code, output_sink)
|
|
41
|
+
<<~RUBY
|
|
42
|
+
require 'json'
|
|
43
|
+
input = JSON.parse($stdin.read)
|
|
44
|
+
|
|
45
|
+
#{code}
|
|
46
|
+
|
|
47
|
+
output = if defined?(run) == 'method'
|
|
48
|
+
run(input)
|
|
49
|
+
elsif defined?(result)
|
|
50
|
+
result
|
|
51
|
+
else
|
|
52
|
+
input
|
|
53
|
+
end
|
|
54
|
+
#{output_sink}
|
|
55
|
+
RUBY
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# NOTE: node also accepts `function transform(input)` for
|
|
59
|
+
# production parity. Plugins authored against the hosted service
|
|
60
|
+
# using `transform` would otherwise silently pass input through.
|
|
61
|
+
def node(code, output_sink)
|
|
62
|
+
<<~JS
|
|
63
|
+
const input = JSON.parse(require('fs').readFileSync(0, 'utf8'));
|
|
64
|
+
|
|
65
|
+
#{code}
|
|
66
|
+
|
|
67
|
+
let output;
|
|
68
|
+
if (typeof run === "function") {
|
|
69
|
+
output = run(input);
|
|
70
|
+
} else if (typeof transform === "function") {
|
|
71
|
+
output = transform(input);
|
|
72
|
+
} else if (typeof result !== "undefined") {
|
|
73
|
+
output = result;
|
|
74
|
+
} else {
|
|
75
|
+
output = input;
|
|
76
|
+
}
|
|
77
|
+
#{output_sink}
|
|
78
|
+
JS
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# NOTE: strips a leading `<?php` tag from user code so plugin
|
|
82
|
+
# authors can write the file as a standalone .php script. The
|
|
83
|
+
# hosted service does the same.
|
|
84
|
+
def php(code, output_sink)
|
|
85
|
+
cleaned = code.sub(/\A\s*<\?php\s*/, '')
|
|
86
|
+
<<~PHP
|
|
87
|
+
<?php
|
|
88
|
+
$input = json_decode(file_get_contents('php://stdin'), true);
|
|
89
|
+
|
|
90
|
+
#{cleaned}
|
|
91
|
+
|
|
92
|
+
if (function_exists('run')) {
|
|
93
|
+
$output = run($input);
|
|
94
|
+
} elseif (isset($result)) {
|
|
95
|
+
$output = $result;
|
|
96
|
+
} else {
|
|
97
|
+
$output = $input;
|
|
98
|
+
}
|
|
99
|
+
#{output_sink}
|
|
100
|
+
PHP
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def for(language, code, output_sink)
|
|
104
|
+
case language.to_s
|
|
105
|
+
when 'python' then python(code, output_sink)
|
|
106
|
+
when 'ruby' then ruby(code, output_sink)
|
|
107
|
+
when 'node' then node(code, output_sink)
|
|
108
|
+
when 'php' then php(code, output_sink)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRMNLP
|
|
4
|
+
# Strategy host for serverless transform execution. Local plugin
|
|
5
|
+
# development hits the Subprocess backend by default — transforms
|
|
6
|
+
# run in trmnlp's own image alongside Ruby, no daemon required.
|
|
7
|
+
# Setting `serverless_daemon_url` in .trmnlp.yml swaps in the Http
|
|
8
|
+
# backend so plugin authors can target a real remote transform
|
|
9
|
+
# daemon (production parity testing, shared team daemons, etc.).
|
|
10
|
+
class TransformClient
|
|
11
|
+
# Mirrors the remote daemon's ExecResponse. `output` is the canonical
|
|
12
|
+
# JSON result; `stdout` carries user prints.
|
|
13
|
+
Result = Data.define(:stdout, :stderr, :output, :exit_code, :duration_ms, :error) do
|
|
14
|
+
def success? = error.nil? && exit_code.zero?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :backend
|
|
18
|
+
|
|
19
|
+
# Returns nil when serverless is disabled in .trmnlp.yml so the
|
|
20
|
+
# pipeline can short-circuit without a per-request check.
|
|
21
|
+
def self.from_config(project_config)
|
|
22
|
+
runtime = project_config.transform_runtime
|
|
23
|
+
return nil if runtime.nil? || runtime.to_s == 'disabled'
|
|
24
|
+
|
|
25
|
+
new(backend: backend_for(project_config))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.backend_for(project_config)
|
|
29
|
+
if (url = project_config.serverless_daemon_url)
|
|
30
|
+
TransformBackend::Http.new(url: url, api_key: project_config.serverless_daemon_api_key)
|
|
31
|
+
else
|
|
32
|
+
TransformBackend::Subprocess.new
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def initialize(backend:)
|
|
37
|
+
@backend = backend
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def execute(code:, language:, stdin: '', timeout_seconds: 30)
|
|
41
|
+
backend.execute(code:, language:, stdin:, timeout_seconds:)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
require_relative 'transform_backend/subprocess'
|
|
47
|
+
require_relative 'transform_backend/http'
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
require_relative 'reporter'
|
|
6
|
+
require_relative 'transform_client'
|
|
7
|
+
|
|
8
|
+
module TRMNLP
|
|
9
|
+
# Pipes assembled merge_variables through src/transform.{py,rb,php,js}
|
|
10
|
+
# when a serverless runtime is configured. Mirrors the hosted
|
|
11
|
+
# transform behavior: the transform receives the data (including the
|
|
12
|
+
# trmnl namespace) on stdin and its stdout JSON replaces the data.
|
|
13
|
+
# Failure modes surface via #error (rendered in the preview UI), not
|
|
14
|
+
# raised.
|
|
15
|
+
class TransformPipeline
|
|
16
|
+
attr_reader :error
|
|
17
|
+
|
|
18
|
+
def initialize(config:, paths:, reporter: Reporter.new)
|
|
19
|
+
@config = config
|
|
20
|
+
@paths = paths
|
|
21
|
+
@reporter = reporter
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call(data)
|
|
25
|
+
@error = nil
|
|
26
|
+
transform_path, inferred_language = paths.transform_file
|
|
27
|
+
return data unless transform_path && client
|
|
28
|
+
|
|
29
|
+
run(transform_path, inferred_language, data)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def reset! = @client = nil
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
attr_reader :config, :paths, :reporter
|
|
37
|
+
|
|
38
|
+
def client = @client ||= TransformClient.from_config(config.project)
|
|
39
|
+
|
|
40
|
+
def run(path, inferred_language, data)
|
|
41
|
+
language = config.plugin.serverless_language || inferred_language
|
|
42
|
+
result = client.execute(code: path.read, stdin: JSON.generate(data), language:)
|
|
43
|
+
return record_failure(result, data) unless result.success?
|
|
44
|
+
|
|
45
|
+
parse_output(result.output, data)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def record_failure(result, fallback)
|
|
49
|
+
@error = result.error || "transform exited #{result.exit_code}: #{result.stderr.strip}"
|
|
50
|
+
reporter.info("transform failed: #{@error}")
|
|
51
|
+
fallback
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def parse_output(output, fallback)
|
|
55
|
+
transformed = JSON.parse(output)
|
|
56
|
+
transformed.is_a?(Hash) ? transformed : wrap_array(transformed)
|
|
57
|
+
rescue JSON::ParserError => e
|
|
58
|
+
@error = "transform produced non-JSON output: #{e.message}"
|
|
59
|
+
reporter.info(@error)
|
|
60
|
+
fallback
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def wrap_array(json) = json.is_a?(Array) ? { data: json } : json
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support'
|
|
4
|
+
require 'active_support/time'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module TRMNLP
|
|
8
|
+
class UserDataAssembler
|
|
9
|
+
DEFAULT_DEVICE_WIDTH = 800
|
|
10
|
+
DEFAULT_DEVICE_HEIGHT = 480
|
|
11
|
+
|
|
12
|
+
def initialize(config:, paths:, transform_pipeline:)
|
|
13
|
+
@config = config
|
|
14
|
+
@paths = paths
|
|
15
|
+
@transform_pipeline = transform_pipeline
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Assembles the merged data hash. The trmnl namespace is built first,
|
|
19
|
+
# layered with static_data / cached polled data / user_data_overrides,
|
|
20
|
+
# then piped through the transform. The trmnl namespace is re-applied
|
|
21
|
+
# after the transform so device, user, and plugin_settings survive
|
|
22
|
+
# even when the transform doesn't pass them through.
|
|
23
|
+
def call(device: {})
|
|
24
|
+
namespace = base_trmnl_data(device:)
|
|
25
|
+
merged = assemble(namespace)
|
|
26
|
+
result = transform_pipeline.call(merged)
|
|
27
|
+
result['trmnl'] = namespace['trmnl']
|
|
28
|
+
result
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def device_from_params(params)
|
|
32
|
+
{ 'width' => params[:width]&.to_i, 'height' => params[:height]&.to_i }.compact
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
attr_reader :config, :paths, :transform_pipeline
|
|
38
|
+
|
|
39
|
+
def assemble(namespace)
|
|
40
|
+
data = namespace.dup
|
|
41
|
+
merge_source_data!(data)
|
|
42
|
+
data.deep_merge!(config.project.user_data_overrides)
|
|
43
|
+
data
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def merge_source_data!(data)
|
|
47
|
+
if config.plugin.static?
|
|
48
|
+
data.merge!(config.plugin.static_data)
|
|
49
|
+
elsif paths.user_data.exist?
|
|
50
|
+
data.merge!(JSON.parse(paths.user_data.read))
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def base_trmnl_data(device:)
|
|
55
|
+
{ 'trmnl' => trmnl_namespace(device:) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def trmnl_namespace(device:)
|
|
59
|
+
{
|
|
60
|
+
'user' => user_namespace,
|
|
61
|
+
'device' => device_namespace(device),
|
|
62
|
+
'system' => { 'timestamp_utc' => Time.now.utc.to_i },
|
|
63
|
+
'plugin_settings' => plugin_settings_namespace
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def user_namespace
|
|
68
|
+
tz = ActiveSupport::TimeZone.find_tzinfo(config.project.time_zone)
|
|
69
|
+
iana = tz.name
|
|
70
|
+
{
|
|
71
|
+
'name' => 'name', 'first_name' => 'first_name', 'last_name' => 'last_name',
|
|
72
|
+
'locale' => 'en', 'time_zone' => ActiveSupport::TimeZone::MAPPING.invert[iana] || iana,
|
|
73
|
+
'time_zone_iana' => iana, 'utc_offset' => tz.utc_offset
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def device_namespace(device)
|
|
78
|
+
{
|
|
79
|
+
'friendly_id' => 'ABC123', 'percent_charged' => 85.0, 'wifi_strength' => 90,
|
|
80
|
+
'height' => device['height'] || DEFAULT_DEVICE_HEIGHT,
|
|
81
|
+
'width' => device['width'] || DEFAULT_DEVICE_WIDTH
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def plugin_settings_namespace
|
|
86
|
+
{
|
|
87
|
+
'instance_name' => 'instance_name',
|
|
88
|
+
'strategy' => config.plugin.strategy,
|
|
89
|
+
'dark_mode' => config.plugin.dark_mode,
|
|
90
|
+
'polling_headers' => config.plugin.polling_headers_encoded,
|
|
91
|
+
'polling_url' => config.plugin.polling_url_text,
|
|
92
|
+
'custom_fields_values' => config.project.custom_fields
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'filewatcher'
|
|
4
|
+
|
|
5
|
+
require_relative 'reporter'
|
|
6
|
+
|
|
7
|
+
module TRMNLP
|
|
8
|
+
class Watcher
|
|
9
|
+
def initialize(config:, user_data_assembler:, transform_pipeline:, reporter: Reporter.new)
|
|
10
|
+
@config = config
|
|
11
|
+
@user_data_assembler = user_data_assembler
|
|
12
|
+
@transform_pipeline = transform_pipeline
|
|
13
|
+
@reporter = reporter
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def start
|
|
17
|
+
@start ||= Thread.new { run }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def on_change(&block)
|
|
21
|
+
@view_change_callback = block
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
attr_reader :config, :user_data_assembler, :transform_pipeline, :reporter
|
|
27
|
+
|
|
28
|
+
def run
|
|
29
|
+
loop do
|
|
30
|
+
watch_cycle
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
reporter.info("error during live render: #{e}")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def watch_cycle
|
|
37
|
+
Filewatcher.new(config.project.watch_paths).watch do |changes|
|
|
38
|
+
reload_config!
|
|
39
|
+
notify(changes)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def reload_config!
|
|
44
|
+
config.project.reload!
|
|
45
|
+
config.plugin.reload!
|
|
46
|
+
transform_pipeline.reset! # config may have changed runtime config
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# NOTE: transform.* changes don't trigger a re-poll — the transform
|
|
50
|
+
# runs inside user_data on every render against the cached polled
|
|
51
|
+
# response (or static_data), so editing the transform updates the
|
|
52
|
+
# preview without re-fetching the API.
|
|
53
|
+
def notify(changes)
|
|
54
|
+
data = user_data_assembler.call
|
|
55
|
+
return unless @view_change_callback
|
|
56
|
+
|
|
57
|
+
changes.each_key { |path| @view_change_callback.call(File.basename(path, '.liquid'), data) }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/trmnlp.rb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRMNLP; end
|
|
4
|
+
require 'oj'
|
|
5
|
+
Oj.mimic_JSON
|
|
6
|
+
require_relative 'trmnlp/errors'
|
|
7
|
+
require_relative 'trmnlp/config'
|
|
8
|
+
require_relative 'trmnlp/context'
|
|
9
|
+
require_relative 'trmnlp/screen'
|
|
10
|
+
require_relative 'trmnlp/version'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# TRMNLP configuration
|
|
2
|
+
# {{ env.VARIABLE }} interpolation is available here
|
|
3
|
+
---
|
|
4
|
+
# auto-reload when files change (`watch: false` to disable)
|
|
5
|
+
watch:
|
|
6
|
+
- .trmnlp.yml
|
|
7
|
+
- src
|
|
8
|
+
|
|
9
|
+
# values of custom fields (defined in src/settings.yml)
|
|
10
|
+
custom_fields: {}
|
|
11
|
+
|
|
12
|
+
# override variables
|
|
13
|
+
variables:
|
|
14
|
+
trmnl: {}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# This script was automatically generated by `trmnlp init` but it's yours to modify!
|
|
4
|
+
# Use this opportunity to set up environment variables, install dependencies, etc.
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
if command -v trmnlp &> /dev/null
|
|
8
|
+
then
|
|
9
|
+
trmnlp "$@"
|
|
10
|
+
exit
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Determine XDG config directory on host OS
|
|
14
|
+
if [ -n "$XDG_CONFIG_HOME" ]; then
|
|
15
|
+
CONFIG_DIR="$XDG_CONFIG_HOME/trmnlp"
|
|
16
|
+
else
|
|
17
|
+
CONFIG_DIR="$HOME/.config/trmnlp"
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
if [ ! -d "$CONFIG_DIR" ]; then
|
|
21
|
+
mkdir -p "$CONFIG_DIR"
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
if command -v docker &> /dev/null
|
|
25
|
+
then
|
|
26
|
+
docker run \
|
|
27
|
+
-it \
|
|
28
|
+
--rm \
|
|
29
|
+
--publish 4567:4567 \
|
|
30
|
+
--volume "$(pwd):/plugin" \
|
|
31
|
+
--volume "$CONFIG_DIR:/root/.config/trmnlp" \
|
|
32
|
+
trmnl/trmnlp "$@"
|
|
33
|
+
exit
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
echo "Install the trmnl_preview gem:
|
|
37
|
+
|
|
38
|
+
gem install trmnl_preview
|
|
39
|
+
|
|
40
|
+
Or install Docker:
|
|
41
|
+
|
|
42
|
+
https://docs.docker.com/get-docker/"
|
|
43
|
+
|
|
44
|
+
exit 1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
full!
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
half horizontal!
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
half vertical!
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
quadrant!
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Changes to this file will be overwritten by `trmnlp pull`.
|
|
3
|
+
#
|
|
4
|
+
# Docs: https://help.trmnl.com/en/articles/10542599-importing-and-exporting-private-plugins#h_581fb988f0
|
|
5
|
+
#
|
|
6
|
+
---
|
|
7
|
+
strategy: polling
|
|
8
|
+
no_screen_padding: 'no'
|
|
9
|
+
dark_mode: 'no'
|
|
10
|
+
static_data: ''
|
|
11
|
+
polling_verb: get
|
|
12
|
+
framework_version: latest
|
|
13
|
+
polling_url: ''
|
|
14
|
+
polling_headers: ''
|
|
15
|
+
name: My Plugin
|
|
16
|
+
refresh_interval: 1440
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Optional serverless transform.
|
|
2
|
+
# Rename this file to `transform.py` (or transform.rb / transform.php / transform.js)
|
|
3
|
+
# and it runs automatically — serverless transforms are enabled by default.
|
|
4
|
+
# (Set `transform_runtime: disabled` in .trmnlp.yml to turn them off.)
|
|
5
|
+
#
|
|
6
|
+
# Define a `run(input)` function that takes the polled response (parsed
|
|
7
|
+
# JSON, including the trmnl namespace) and returns any JSON-serialisable
|
|
8
|
+
# value. The bundled transform-runtime wraps this and handles stdin/stdout.
|
|
9
|
+
# This matches the hosted serverless contract — the same code runs in production.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run(input):
|
|
13
|
+
# Reshape `input` however you like, then return.
|
|
14
|
+
return input
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/trmnlp/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'trmnl_preview'
|
|
7
|
+
spec.version = TRMNLP::VERSION
|
|
8
|
+
spec.authors = ['Rockwell Schrock']
|
|
9
|
+
spec.email = ['rockwell@schrock.me']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'Local web server to preview TRMNL plugins'
|
|
12
|
+
spec.description = 'Automatically rebuild and preview TRNML plugins in multiple views'
|
|
13
|
+
spec.homepage = 'https://github.com/usetrmnl/trmnlp'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
spec.required_ruby_version = '>= 3.4'
|
|
16
|
+
|
|
17
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
|
18
|
+
|
|
19
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
20
|
+
spec.metadata['source_code_uri'] = 'https://github.com/usetrmnl/trmnlp'
|
|
21
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
22
|
+
|
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
|
24
|
+
[
|
|
25
|
+
'bin/**/*',
|
|
26
|
+
'db/**/*',
|
|
27
|
+
'lib/**/*',
|
|
28
|
+
'templates/**/{*,.*}',
|
|
29
|
+
'web/**/*',
|
|
30
|
+
'CHANGELOG.md',
|
|
31
|
+
'LICENSE.txt',
|
|
32
|
+
'README.md',
|
|
33
|
+
'trmnl_preview.gemspec'
|
|
34
|
+
].flat_map { |glob| Dir[glob] }
|
|
35
|
+
end
|
|
36
|
+
spec.bindir = 'bin'
|
|
37
|
+
spec.executables = ['trmnlp']
|
|
38
|
+
spec.require_paths = ['lib']
|
|
39
|
+
|
|
40
|
+
# Web server
|
|
41
|
+
spec.add_dependency 'puma', '~> 8.0'
|
|
42
|
+
spec.add_dependency 'rackup', '~> 2.2'
|
|
43
|
+
spec.add_dependency 'sinatra', '~> 4.1'
|
|
44
|
+
|
|
45
|
+
# HTML rendering
|
|
46
|
+
spec.add_dependency 'activesupport', '~> 8.0'
|
|
47
|
+
spec.add_dependency 'trmnl-liquid', '~> 0.7.0'
|
|
48
|
+
|
|
49
|
+
# PNG rendering
|
|
50
|
+
# spec.add_dependency 'puppeteer-ruby', '~> 0.45.6'
|
|
51
|
+
spec.add_dependency 'mini_magick', '~> 5.3'
|
|
52
|
+
spec.add_dependency 'selenium-webdriver', '~> 4.44'
|
|
53
|
+
|
|
54
|
+
# Utilities
|
|
55
|
+
spec.add_dependency 'cgi', '~> 0.5'
|
|
56
|
+
spec.add_dependency 'faraday', '~> 2.1'
|
|
57
|
+
spec.add_dependency 'faraday-multipart', '~> 1.1'
|
|
58
|
+
spec.add_dependency 'filewatcher', '~> 3.0'
|
|
59
|
+
spec.add_dependency 'oj', '~> 3.17'
|
|
60
|
+
spec.add_dependency 'rexml', '~> 3.4'
|
|
61
|
+
spec.add_dependency 'rubyzip', '~> 3.3'
|
|
62
|
+
spec.add_dependency 'thor', '~> 1.3'
|
|
63
|
+
spec.add_dependency 'tzinfo-data', '~> 1.2025'
|
|
64
|
+
spec.add_dependency 'xdg', '~> 10.2'
|
|
65
|
+
|
|
66
|
+
# For more information and examples about making a new gem, check out our
|
|
67
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
68
|
+
end
|