trmnl_preview 0.9.0 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbbc567f2937e7dd1aabc75991df616728f88fa858d797384a4a63453265be5c
4
- data.tar.gz: 1b1e543aa9ea1dddbb456f061f1538c22cc0edd849fc6faced3d67fa5f682677
3
+ metadata.gz: 445d3f901f97e586cf2bfeb3dd8286cf2b02cb3c14d8380f50f0d34d35d7d97f
4
+ data.tar.gz: e40390fff7ec0ca67127209eacd5de1e096fb800a91148c1068c91aa66748411
5
5
  SHA512:
6
- metadata.gz: 7b0ab9feab22239ce5e2eaa6c9b2fc7d1f889927e267c14d83eb6ec02e60a973acb9194fee59cae709ea7640c43741186788588b33e9d76ac023a5d5f09280d4
7
- data.tar.gz: c18a319c45dec945b5c8f71d56c47f4478b8a82bceaed429b475cce4f57cb8a29541bcf7b1312e8fb9624513aebbd434d05c6e64f02d32322e9628cffc08553f
6
+ metadata.gz: 8bd5fe746adb49697c526ff51256ece9febc2172aa5177af97399a45664f6ce2925f019ed73efdc0bb0515f2405abb45583faf27d8046feb706ccbde9c9e8c46
7
+ data.tar.gz: 22ec5594e73ec2903e5f6d83a0b62699cc7cc4543aca6e0bf8f8b5f376dd3418be4ba2b6d2c7d2ef7af63cedb6c451612333a00f5e6dc66dbb60f6f76383cf27
data/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
1
 
2
2
  # Changelog
3
3
 
4
+ ## 0.10.0
5
+
6
+ - Added support for the `description` plugin setting, an optional one-line summary of the plugin. `trmnlp init` scaffolds the key in `src/settings.yml`, `trmnlp lint` reports descriptions longer than 35 characters (the hosted service's limit), and `trmnlp list` shows a DESCRIPTION column when any plugin has one. Only the length is checked. Storing the value needs the matching hosted service release; until then `trmnlp push` drops it, because it rewrites the local `settings.yml` from the server's response.
7
+ - `trmnlp push` now offers to delete the server-side transform script when the project has no local `transform.*` file. Previously, deleting the file locally was a silent no-op: the server kept executing its copy and every `trmnlp pull` re-created the file. Answering yes sends `serverless_language: none` in the pushed settings.yml, which the server consumes as an explicit removal request. `--force` pushes skip the question and behave exactly as before; a failed server check never blocks the push.
8
+
4
9
  ## 0.9.0
5
10
 
6
11
  - Added OAuth2 support for local plugin preview (beta). Configure a provider with the flat `oauth_*` keys in `src/settings.yml` (authorize and token URLs, scopes, optional PKCE), which round-trip through `trmnlp push` and `pull`. Set `TRMNL_OAUTH_CLIENT_ID` and `TRMNL_OAUTH_CLIENT_SECRET` in your environment (credentials stay local and are never synced), and register `http://localhost:4567/oauth/callback` as the redirect URI in your OAuth app. `trmnlp serve` shows a Connect banner that runs the authorization code flow in the browser, stores the tokens in the cache directory (never in the project), and refreshes them before they expire. The access token is exposed to polling templates as `{{ oauth_access_token }}`, matching the hosted service. This is a beta feature; please report incorrect behaviour at https://github.com/usetrmnl/trmnlp/issues.
data/README.md CHANGED
@@ -429,6 +429,8 @@ The `settings.yml` file is part of the plugin definition, and is uploaded and do
429
429
 
430
430
  `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.
431
431
 
432
+ `description:` is an optional one-line summary of the plugin, up to 35 characters. `trmnlp lint` reports anything longer, and `trmnlp list` shows it next to each plugin name.
433
+
432
434
  See [TRMNL documentation](https://help.trmnl.com/en/articles/10542599-importing-and-exporting-private-plugins#h_581fb988f0) for details on this file's contents.
433
435
 
434
436
 
@@ -9,6 +9,8 @@ module TRMNLP
9
9
  Options = Data.define(:dir, :quiet)
10
10
 
11
11
  PRIVATE_PLUGIN_ID = 37
12
+ ID_WIDTH = 8
13
+ RULE_WIDTH = 50
12
14
 
13
15
  def call
14
16
  authenticate!
@@ -25,15 +27,55 @@ module TRMNLP
25
27
  end
26
28
 
27
29
  reporter.info "Your plugins:\n\n"
28
- reporter.info ' ID NAME'
29
- reporter.info " #{'-' * 50}"
30
+ report_table(plugins)
31
+
32
+ reporter.info "\nTo clone a plugin:"
33
+ reporter.info ' trmnlp clone [folder_name] [id]'
34
+ end
35
+
36
+ private
37
+
38
+ def report_table(plugins)
39
+ return report_names(plugins) if plugins.none? { |plugin| description(plugin) }
40
+
41
+ report_names_and_descriptions(plugins)
42
+ end
43
+
44
+ def report_names(plugins)
45
+ ids = id_width(plugins)
46
+ report_header format("%-#{ids}s %s", 'ID', 'NAME')
30
47
 
31
48
  plugins.each do |plugin|
32
- reporter.info format(' %-8s %s', plugin['id'], plugin['name'])
49
+ reporter.info format(" %-#{ids}s %s", plugin['id'], plugin['name'])
33
50
  end
51
+ end
34
52
 
35
- reporter.info "\nTo clone a plugin:"
36
- reporter.info ' trmnlp clone [folder_name] [id]'
53
+ def report_names_and_descriptions(plugins)
54
+ ids = id_width(plugins)
55
+ names = column_width(plugins.map { |plugin| plugin['name'] }, 'NAME'.length)
56
+ report_header format("%-#{ids}s %-#{names}s %s", 'ID', 'NAME', 'DESCRIPTION')
57
+
58
+ plugins.each do |plugin|
59
+ row = format(" %-#{ids}s %-#{names}s %s", plugin['id'], plugin['name'], description(plugin))
60
+ reporter.info row.rstrip
61
+ end
62
+ end
63
+
64
+ # BYOS servers return a UUID for plugins created there and the original
65
+ # number for plugins imported from the hosted service, so one response
66
+ # can hold both widths.
67
+ def id_width(plugins) = column_width(plugins.map { |plugin| plugin['id'] }, ID_WIDTH)
68
+
69
+ def column_width(values, minimum) = [*values.map { |value| value.to_s.length }, minimum].max
70
+
71
+ def report_header(text)
72
+ reporter.info " #{text}"
73
+ reporter.info " #{'-' * [text.length, RULE_WIDTH].max}"
74
+ end
75
+
76
+ def description(plugin)
77
+ value = plugin['description'].to_s.strip
78
+ value unless value.empty?
37
79
  end
38
80
  end
39
81
  end
@@ -38,11 +38,8 @@ module TRMNLP
38
38
  raise Aborted, 'aborting' unless %w[y yes].include?(answer)
39
39
  end
40
40
 
41
- Zip::File.open(ZIP_PATH, create: true) do |zip_file|
42
- paths.src_files.each do |file|
43
- zip_file.add(File.basename(file), file)
44
- end
45
- end
41
+ removal_requested = !is_new && confirm_server_transform_removal?(api, plugin_settings_id)
42
+ build_archive(removal_requested)
46
43
 
47
44
  response = api.post_plugin_setting_archive(plugin_settings_id, ZIP_PATH)
48
45
  paths.plugin_config.write(response.dig('data', 'settings_yaml'))
@@ -72,6 +69,51 @@ module TRMNLP
72
69
  ensure
73
70
  FileUtils.rm_f(ZIP_PATH)
74
71
  end
72
+
73
+ private
74
+
75
+ def build_archive(removal_requested)
76
+ Zip::File.open(ZIP_PATH, create: true) do |zip_file|
77
+ paths.src_files.each do |file|
78
+ next if removal_requested && file == paths.plugin_config
79
+
80
+ zip_file.add(File.basename(file), file)
81
+ end
82
+ if removal_requested
83
+ zip_file.get_output_stream('settings.yml') { |entry| entry.write(settings_yaml_with_removal_request) }
84
+ end
85
+ end
86
+ end
87
+
88
+ # Without this, deleting a local transform.* is a silent no-op: the server
89
+ # keeps executing its copy and every pull re-creates the file. The pushed
90
+ # settings.yml carries `serverless_language: none`, which the importer
91
+ # consumes as an explicit removal request.
92
+ def confirm_server_transform_removal?(api, plugin_settings_id)
93
+ return false if options.force
94
+ return false if paths.transform_file.first
95
+ return false unless server_transform?(api, plugin_settings_id)
96
+
97
+ question = 'The server has a transform script, but this project has no transform file. ' \
98
+ 'Delete it on the server too? (y/N) '
99
+ %w[y yes].include?(prompt(question).downcase)
100
+ end
101
+
102
+ def server_transform?(api, plugin_settings_id)
103
+ archive = api.get_plugin_setting_archive(plugin_settings_id)
104
+ begin
105
+ Zip::File.open(archive.path) { |zip_file| zip_file.any? { |entry| entry.name.start_with?('transform.') } }
106
+ ensure
107
+ archive.close
108
+ end
109
+ rescue Error
110
+ # Advisory check only — an unreadable archive must not block the push.
111
+ false
112
+ end
113
+
114
+ def settings_yaml_with_removal_request
115
+ config.plugin.settings.merge('serverless_language' => 'none').to_yaml
116
+ end
75
117
  end
76
118
  end
77
119
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../check'
4
+
5
+ module TRMNLP
6
+ module Lint
7
+ module Checks
8
+ # Matches the hosted service's limit. There is no upstream file to sync
9
+ # from, so update this by hand when that limit changes, the same way
10
+ # db/data/form_fields.yml is kept current.
11
+ class DescriptionLength < Check
12
+ MAX_LENGTH = 35
13
+ MESSAGE = "Description should be <= #{MAX_LENGTH} characters long.".freeze
14
+
15
+ private
16
+
17
+ def pass? = source.plugin_description.length <= MAX_LENGTH
18
+ end
19
+ end
20
+ end
21
+ end
@@ -16,6 +16,7 @@ module TRMNLP
16
16
  end
17
17
 
18
18
  def plugin_name = settings['name'].to_s
19
+ def plugin_description = settings['description'].to_s
19
20
  def settings = config.plugin.settings
20
21
  def custom_field_values = config.project.custom_fields
21
22
  def custom_field_definitions = config.plugin.custom_field_definitions
data/lib/trmnlp/lint.rb CHANGED
@@ -4,6 +4,7 @@ require_relative 'lint/check'
4
4
  require_relative 'lint/source'
5
5
  require_relative 'lint/checks/title_casing'
6
6
  require_relative 'lint/checks/title_length'
7
+ require_relative 'lint/checks/description_length'
7
8
  require_relative 'lint/checks/layouts_have_content'
8
9
  require_relative 'lint/checks/no_async_functions'
9
10
  require_relative 'lint/checks/waits_for_dom_load'
@@ -23,6 +24,7 @@ module TRMNLP
23
24
  CHECKS = [
24
25
  Checks::TitleCasing,
25
26
  Checks::TitleLength,
27
+ Checks::DescriptionLength,
26
28
  Checks::LayoutsHaveContent,
27
29
  Checks::NoAsyncFunctions,
28
30
  Checks::WaitsForDomLoad,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TRMNLP
4
- VERSION = '0.9.0'
4
+ VERSION = '0.10.0'
5
5
  end
@@ -13,4 +13,5 @@ framework_version: latest
13
13
  polling_url: ''
14
14
  polling_headers: ''
15
15
  name: My Plugin
16
+ description: ''
16
17
  refresh_interval: 1440
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.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rockwell Schrock
@@ -305,6 +305,7 @@ files:
305
305
  - lib/trmnlp/lint.rb
306
306
  - lib/trmnlp/lint/check.rb
307
307
  - lib/trmnlp/lint/checks/custom_fields_used.rb
308
+ - lib/trmnlp/lint/checks/description_length.rb
308
309
  - lib/trmnlp/lint/checks/form_fields_valid.rb
309
310
  - lib/trmnlp/lint/checks/highcharts_animations_disabled.rb
310
311
  - lib/trmnlp/lint/checks/highcharts_elements_unique.rb