trmnl_preview 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23e1959e442066534a715efcc371a9c7d4cb41c7dda22727712b12b7b1eb90af
4
- data.tar.gz: fbf07c5d835d4981b0380ce774fdfb74edab699c16772e187776ba58a1aee152
3
+ metadata.gz: 2dabcf8ddd9b11d8ce8fc76e389820c7cd4f2d3b48db7aa7848896f9dfb6ec59
4
+ data.tar.gz: 467322fc910fdfed1ed5c37bb9773a7256d3d8f1f44683e2c9e4f3882b4ab7df
5
5
  SHA512:
6
- metadata.gz: 8caa7eeef62cb562df06f7e0b3e1fcbf025cabe1f98fcda83fc365a0ee0fa9d8ce70fca9a3c16514a8ce3c320296c234e96e358d45cf3481e2b0451772e8866a
7
- data.tar.gz: 01b1248e629516ab0c1857138807a27066f7b4e97be26024dd5e3365152d399e3e301fefcb864eb8e355546a5d5d6149f7c9851a345a3c8f09819099cd54c5bc
6
+ metadata.gz: 75747b93b8aa45ac964b1ae87ef0dd50134c836bf5bc4bef19e47f166132b1dfca6edf8744f967702be6ffdc4c80c7b9b725646443053708f2d74df37a8537e9
7
+ data.tar.gz: b6fa8f755ab31f3dae9a3c2773ced039f646c7363288ae27512d0e3a95e72143093ca2869cb26a402084dd1b66c6c2dfc29a022ef8fed84bbb6e18fa5ffed347
data/CHANGELOG.md CHANGED
@@ -1,6 +1,17 @@
1
1
 
2
2
  # Changelog
3
3
 
4
+ ## 0.14.2
5
+
6
+ - A failed transform prints its stderr once. The `transform failed:` line repeated the whole stack trace that the `transform stderr:` line had just printed; it now names only the exit code. The preview page still shows the full error. (#148)
7
+ - When a polling URL answers 401 and an OAuth account is connected, trmnlp refreshes the token and polls once more, as the hosted service does. Before, it refreshed only once the stored expiry had passed, so a token the provider revoked early kept failing until you reconnected. (#150)
8
+ - A polling response that is not a 200 still reaches the markup and the transform, as on the hosted service, with a warning for a status outside 2xx. trmnlp replaced it with `{}`, so a transform that follows a 202 "pending job" body (`job_id`, `poll`) got nothing locally. (#146)
9
+ - `trmnlp lint` reports bracketed Framework classes the Framework never generates, such as `w--[192px]` (sizes stop at 128px), `gap--[60px]` and `rounded--[51px]` (both stop at 50px), `h--[6cqw]` (height takes `cqh`) and, from 3.2.0, a screen-prefixed `md:gap--[20px]`. They match no CSS rule, so they do nothing: a flag sized `lg:w--[192px]` stayed at its smaller width on the X with no warning. The ranges are checked against every 3.x `plugins.css`, and plugins on Framework 2 or earlier are skipped, since v2 generated wider sizes. (#149)
10
+
11
+ ## 0.14.1
12
+
13
+ - `trmnlp lint` reports a `recipe_overview` under 100 words. Below that, TRMNL serves the public recipe page with `robots: noindex`, so the recipe never appears in Google or other search engines, and nothing said so: the page looked fine, and only its HTML gave it away. A blank overview still passes, since private plugins have none. (#144)
14
+
4
15
  ## 0.14.0
5
16
 
6
17
  - `trmnlp serve` and `build` print what the transform writes to stdout and stderr (`console.log`, `print`, `puts`), one `transform stdout:` / `transform stderr:` line each, so a transform can be debugged without returning its logs in the data. (#128)
data/README.md CHANGED
@@ -434,7 +434,7 @@ The `settings.yml` file is part of the plugin definition, and is uploaded and do
434
434
 
435
435
  `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.
436
436
 
437
- `recipe_overview:` is optional free text for a published recipe, with no length limit. TRMNL shows it on the public recipe page with its line breaks kept, and its first 120 characters feed the page's link preview. An overview of at least 100 words lets the recipe appear in Google and other search engines. Put credits and links in an About This Plugin field instead. Leave the key out to keep the overview already on TRMNL; an empty value clears it.
437
+ `recipe_overview:` is optional free text for a published recipe, with no length limit. TRMNL shows it on the public recipe page with its line breaks kept, and its first 120 characters feed the page's link preview. An overview of at least 100 words lets the recipe appear in Google and other search engines; `trmnlp lint` reports one that is shorter. Put credits and links in an About This Plugin field instead. Leave the key out to keep the overview already on TRMNL; an empty value clears it.
438
438
 
439
439
  See [TRMNL documentation](https://help.trmnl.com/en/articles/10542599-importing-and-exporting-private-plugins#h_581fb988f0) for details on this file's contents.
440
440
 
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../check'
4
+
5
+ module TRMNLP
6
+ module Lint
7
+ module Checks
8
+ # Reports bracketed Framework classes the Framework never generates, one
9
+ # finding per class. They match no rule, so they do nothing, and nothing
10
+ # says so: `lg:w--[192px]` leaves the element at its smaller width.
11
+ #
12
+ # The ranges mirror the loops in the Framework's utilities/_size.scss,
13
+ # _gap.scss and _rounded.scss, and hold for every 3.x release's
14
+ # plugins.css. v2 generated wider sizes, so earlier versions are skipped;
15
+ # prefixed gaps (`md:gap--[20px]`) existed until 3.2.0. There is no
16
+ # upstream file to sync from, so update this by hand when the Framework
17
+ # changes, the same way db/data/form_fields.yml is kept current.
18
+ class ArbitraryValuesInRange < Check
19
+ # Each family as written before the bracket: `w--[Npx]`, `w--max-[Npx]`.
20
+ SIZE_FAMILIES = %w[w-- h-- w--min- w--max- h--min- h--max-].freeze
21
+
22
+ # family => { unit => max }. Every range starts at 0 and steps by 1.
23
+ RANGES = SIZE_FAMILIES.to_h do |family|
24
+ [family, { 'px' => 128, (family.start_with?('w') ? 'cqw' : 'cqh') => 100 }]
25
+ end.merge('gap--' => { 'px' => 50 }, 'rounded--' => { 'px' => 50 }).freeze
26
+
27
+ # `gap--[Npx]` and `rounded--[Npx]` exist only without a screen prefix.
28
+ PREFIXABLE = SIZE_FAMILIES
29
+
30
+ FIRST_VERSION = Gem::Version.new('3.0.0')
31
+ UNPREFIXED_GAP_VERSION = Gem::Version.new('3.2.0')
32
+
33
+ PATTERN = /
34
+ (?<![\w:-])
35
+ (?<prefix>(?:[a-z0-9-]+:)*)
36
+ (?<family>w--min-|w--max-|h--min-|h--max-|w--|h--|gap--|rounded--)
37
+ \[(?<value>[\d.]+)(?<unit>[a-z]+)\]
38
+ /x
39
+
40
+ def issues
41
+ return [] if version < FIRST_VERSION
42
+
43
+ source.all_markup.scan(PATTERN).uniq.filter_map do |prefix, family, value, unit|
44
+ css_class = "#{prefix}#{family}[#{value}#{unit}]"
45
+ reason = problem(prefix, family, value, unit)
46
+ { message: "'#{css_class}' has no effect: #{reason}." } if reason
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def version = @version ||= Gem::Version.new(source.framework_version.number)
53
+
54
+ def problem(prefix, family, value, unit)
55
+ max = RANGES.dig(family, unit)
56
+ return "the Framework has no '#{unit}' values for '#{family}'" unless max
57
+ return "'#{family}[Npx]' takes no screen prefix" if prefix_unsupported?(prefix, family)
58
+ return nil if value.match?(/\A\d+\z/) && value.to_i <= max
59
+
60
+ "the Framework generates '#{family}[N#{unit}]' from 0 to #{max} in whole numbers"
61
+ end
62
+
63
+ def prefix_unsupported?(prefix, family)
64
+ !prefix.empty? && !PREFIXABLE.include?(family) &&
65
+ (family == 'rounded--' || version >= UNPREFIXED_GAP_VERSION)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../check'
4
+
5
+ module TRMNLP
6
+ module Lint
7
+ module Checks
8
+ # Below this many words the hosted service serves the public recipe page
9
+ # with `robots: noindex`, so search engines drop it. The overview is
10
+ # optional (private plugins never show one), so a blank value passes.
11
+ # Words are counted on whitespace; there is no upstream count to match.
12
+ class RecipeOverviewLength < Check
13
+ MIN_WORDS = 100
14
+ MESSAGE = "Recipe overview should be at least #{MIN_WORDS} words long " \
15
+ 'for the recipe page to appear in Google and other search engines.'.freeze
16
+
17
+ private
18
+
19
+ def pass?
20
+ words = source.recipe_overview.split.size
21
+ words.zero? || words >= MIN_WORDS
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -17,6 +17,8 @@ module TRMNLP
17
17
 
18
18
  def plugin_name = settings['name'].to_s
19
19
  def plugin_description = settings['description'].to_s
20
+ def recipe_overview = settings['recipe_overview'].to_s
21
+ def framework_version = config.plugin.framework_version
20
22
  def settings = config.plugin.settings
21
23
  def custom_field_values = config.project.custom_fields
22
24
  def custom_field_definitions = config.plugin.custom_field_definitions
data/lib/trmnlp/lint.rb CHANGED
@@ -5,11 +5,13 @@ require_relative 'lint/source'
5
5
  require_relative 'lint/checks/title_casing'
6
6
  require_relative 'lint/checks/title_length'
7
7
  require_relative 'lint/checks/description_length'
8
+ require_relative 'lint/checks/recipe_overview_length'
8
9
  require_relative 'lint/checks/layouts_have_content'
9
10
  require_relative 'lint/checks/no_async_functions'
10
11
  require_relative 'lint/checks/waits_for_dom_load'
11
12
  require_relative 'lint/checks/limited_inline_styles'
12
13
  require_relative 'lint/checks/no_size_classes'
14
+ require_relative 'lint/checks/arbitrary_values_in_range'
13
15
  require_relative 'lint/checks/no_opacity'
14
16
  require_relative 'lint/checks/highcharts_animations_disabled'
15
17
  require_relative 'lint/checks/highcharts_elements_unique'
@@ -25,11 +27,13 @@ module TRMNLP
25
27
  Checks::TitleCasing,
26
28
  Checks::TitleLength,
27
29
  Checks::DescriptionLength,
30
+ Checks::RecipeOverviewLength,
28
31
  Checks::LayoutsHaveContent,
29
32
  Checks::NoAsyncFunctions,
30
33
  Checks::WaitsForDomLoad,
31
34
  Checks::LimitedInlineStyles,
32
35
  Checks::NoSizeClasses,
36
+ Checks::ArbitraryValuesInRange,
33
37
  Checks::NoOpacity,
34
38
  Checks::HighchartsAnimationsDisabled,
35
39
  Checks::HighchartsElementsUnique,
@@ -45,6 +45,12 @@ module TRMNLP
45
45
 
46
46
  def disconnect = token_store.clear
47
47
 
48
+ # For a token the provider rejected before its expiry. nil when there is nothing to refresh.
49
+ def force_refresh!
50
+ stored = token_store.read
51
+ refreshed(stored) if stored&.access_token && stored.refresh_token
52
+ end
53
+
48
54
  private
49
55
 
50
56
  attr_reader :provider, :token_store, :client
data/lib/trmnlp/poller.rb CHANGED
@@ -43,21 +43,28 @@ module TRMNLP
43
43
  attr_reader :config, :paths, :oauth_session, :reporter
44
44
 
45
45
  def aggregate_responses
46
- # Resolve once per poll (it refreshes as a side effect), then share across
47
- # every URL, header, and body render.
48
- oauth_variables = oauth_session.liquid_variables
49
- urls = config.plugin.polling_urls(extra_variables: oauth_variables)
50
- responses = urls.map { |url| fetch_one(url, oauth_variables) }
46
+ fetched = fetch_all
47
+ # Like the hosted service: a token rejected before its expiry gets one refresh and retry.
48
+ fetched = fetch_all if fetched.any? { |_url, response| response.status == 401 } && oauth_session.force_refresh!
49
+ responses = fetched.map { |url, response| parse_response(response, url) }
51
50
  return responses.first if responses.size == 1
52
51
 
53
52
  responses.each_with_index.with_object({}) { |(r, i), h| h["IDX_#{i}"] = r }
54
53
  end
55
54
 
55
+ def fetch_all
56
+ # Resolve once per attempt (it refreshes as a side effect), then share across
57
+ # every URL, header, and body render.
58
+ oauth_variables = oauth_session.liquid_variables
59
+ urls = config.plugin.polling_urls(extra_variables: oauth_variables)
60
+ urls.map { |url| [url, fetch_one(url, oauth_variables)] }
61
+ end
62
+
56
63
  def fetch_one(url, oauth_variables)
57
64
  verb = config.plugin.polling_verb.upcase
58
65
  response = perform_request(url, verb, oauth_variables)
59
66
  reporter.info("#{verb} #{url} — received #{response.body.length} bytes (#{response.status} status)")
60
- parse_response(response)
67
+ response
61
68
  end
62
69
 
63
70
  def perform_request(url, verb, oauth_variables)
@@ -69,17 +76,13 @@ module TRMNLP
69
76
  end
70
77
  end
71
78
 
72
- def parse_response(response)
73
- return parse_failure(response.body) unless response.status == 200
74
-
79
+ # Like the hosted service: an error status is reported, but its body still reaches the data.
80
+ def parse_response(response, url)
81
+ success = (200..299).cover?(response.status)
82
+ reporter.info(reporter.yellow("warning: HTTP #{response.status} from #{url}")) unless success
75
83
  parse_body(response.body, response.headers['content-type'])
76
84
  end
77
85
 
78
- def parse_failure(body)
79
- reporter.info(body)
80
- {}
81
- end
82
-
83
86
  def parse_body(body, content_type_header)
84
87
  content_type = content_type_header&.split(';')&.first&.strip
85
88
  case content_type
@@ -55,7 +55,7 @@ module TRMNLP
55
55
 
56
56
  def record_failure(result, fallback)
57
57
  @error = result.error || "transform exited #{result.exit_code}: #{result.stderr.strip}"
58
- reporter.info("transform failed: #{@error}")
58
+ reporter.info("transform failed: #{result.error || "exited #{result.exit_code}"}")
59
59
  fallback
60
60
  end
61
61
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TRMNLP
4
- VERSION = '0.14.0'
4
+ VERSION = '0.14.2'
5
5
  end
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.14.0
4
+ version: 0.14.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rockwell Schrock
@@ -304,6 +304,7 @@ files:
304
304
  - lib/trmnlp/image_quantizer.rb
305
305
  - lib/trmnlp/lint.rb
306
306
  - lib/trmnlp/lint/check.rb
307
+ - lib/trmnlp/lint/checks/arbitrary_values_in_range.rb
307
308
  - lib/trmnlp/lint/checks/custom_fields_used.rb
308
309
  - lib/trmnlp/lint/checks/description_length.rb
309
310
  - lib/trmnlp/lint/checks/form_fields_valid.rb
@@ -315,6 +316,7 @@ files:
315
316
  - lib/trmnlp/lint/checks/no_async_functions.rb
316
317
  - lib/trmnlp/lint/checks/no_opacity.rb
317
318
  - lib/trmnlp/lint/checks/no_size_classes.rb
319
+ - lib/trmnlp/lint/checks/recipe_overview_length.rb
318
320
  - lib/trmnlp/lint/checks/title_casing.rb
319
321
  - lib/trmnlp/lint/checks/title_length.rb
320
322
  - lib/trmnlp/lint/checks/waits_for_dom_load.rb