trmnl_preview 0.13.2 → 0.14.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: 905205771ecf23f22c55a3d48835e6ee75ea90d4d2b55638c6ebea0160173578
4
- data.tar.gz: 663c559a8b71f943023fae8e18ea2e494c8df04e2d5fe81ffd34ce19a6d4d736
3
+ metadata.gz: 23e1959e442066534a715efcc371a9c7d4cb41c7dda22727712b12b7b1eb90af
4
+ data.tar.gz: fbf07c5d835d4981b0380ce774fdfb74edab699c16772e187776ba58a1aee152
5
5
  SHA512:
6
- metadata.gz: d8fbb97e1eb0ccc38a54ab7820de2837e7ef27b1bc52faed278b912a1ad709aee63f5b58860cbe4dedbd9fac7a5b7b3bce5fb68f2a750a0c064bb489a4846bc5
7
- data.tar.gz: 0d3ffc25d6567a13c125bd332be0fd76ec2b018e4de2e144b3e36ac1e4dc99882b6c802c31bbfdcac86e104b00b3448e8d1d4d61733aad953325a67b9399ca0c
6
+ metadata.gz: 8caa7eeef62cb562df06f7e0b3e1fcbf025cabe1f98fcda83fc365a0ee0fa9d8ce70fca9a3c16514a8ce3c320296c234e96e358d45cf3481e2b0451772e8866a
7
+ data.tar.gz: 01b1248e629516ab0c1857138807a27066f7b4e97be26024dd5e3365152d399e3e301fefcb864eb8e355546a5d5d6149f7c9851a345a3c8f09819099cd54c5bc
data/CHANGELOG.md CHANGED
@@ -1,6 +1,13 @@
1
1
 
2
2
  # Changelog
3
3
 
4
+ ## 0.14.0
5
+
6
+ - `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)
7
+ - The OAuth token exchange and refresh honor `oauth_token_request_auth_method` the way the hosted service does: HTTP Basic only for `header`, otherwise the client credentials go in the request body. trmnlp used HTTP Basic whenever a client secret was set, so a provider that wants the credentials in the body rejected the callback with `invalid_client: client_id is required`. A plugin that relied on HTTP Basic locally sets `oauth_token_request_auth_method: header`, which the hosted service already needs. (#121)
8
+ - `polling_headers` are parsed the way the hosted service parses them: one header per line, `Name: Value` as well as `Name=Value`, a JSON object, and values that contain `=`. Before, trmnlp split only on `&`, so headers saved one per line made the whole poll fail with "cannot include CR/LF" and the markup and transform got no polled data (no `IDX_0`/`IDX_1`). (#127)
9
+ - `trmnlp lint` no longer reports a custom field as unused when only the serverless transform (`src/transform.{py,rb,php,js}`) reads it. Plugins that move their data handling into the transform, reading fields from `input["trmnl"]["plugin_settings"]["custom_fields_values"]`, got one "is not used in form fields or markup" warning per field. The check now searches the transform as well, and its message names it. (#136)
10
+
4
11
  ## 0.13.2
5
12
 
6
13
  - `google_photos_picker`, `json`, `modal_trigger` and `oauth_provider_select` are known form field types, so `trmnlp lint`, `build` and `serve` no longer warn "unknown field_type" for them. The hosted service renders all four. (#137)
data/README.md CHANGED
@@ -334,6 +334,7 @@ oauth_scopes: "read:user user:email"
334
334
  oauth_pkce_enabled: "true" # optional, default false
335
335
  # oauth_scope_separator: " " # optional; some providers use ","
336
336
  # oauth_refresh_url: https://... # optional; defaults to oauth_token_url
337
+ # oauth_token_request_auth_method: header # optional; "header" sends the client credentials as HTTP Basic, otherwise they go in the body
337
338
  ```
338
339
 
339
340
  Your OAuth app credentials stay local and are never synced, so set them in your environment:
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'cgi'
4
+ require 'json'
4
5
  require 'yaml'
5
6
 
6
7
  require_relative '../errors'
@@ -113,13 +114,34 @@ module TRMNLP
113
114
  project_config.with_custom_fields(value, extra_variables:)
114
115
  end
115
116
 
116
- def string_to_hash(str, delimiter: '=')
117
- str.split('&').map do |k_v|
118
- key, value = k_v.split(delimiter)
117
+ # Mirrors the hosted service's Plugins::Helpers::Transformer#string_to_hash.
118
+ def string_to_hash(str)
119
+ return json_to_hash(str) if str.strip.start_with?('{')
120
+
121
+ str.split(/[\n&]/).filter_map do |pair|
122
+ pair = pair.strip
123
+ key, value = pair.split(delimiter_for(pair), 2)
119
124
  next if value.nil?
120
125
 
121
- { key => CGI.unescape_uri_component(value) }
122
- end.compact.reduce({}, :merge)
126
+ [key.strip, CGI.unescape_uri_component(value.strip)]
127
+ end.to_h
128
+ end
129
+
130
+ # ": " (not ":") so an "https://" URL isn't mistaken for a "Name: Value" header.
131
+ def delimiter_for(pair)
132
+ equals = pair.index('=')
133
+ colon = pair.index(': ')
134
+ return '=' if colon.nil?
135
+ return ': ' if equals.nil?
136
+
137
+ colon < equals ? ': ' : '='
138
+ end
139
+
140
+ def json_to_hash(str)
141
+ parsed = JSON.parse(str)
142
+ parsed.is_a?(Hash) ? parsed.transform_values(&:to_s) : {}
143
+ rescue JSON::ParserError
144
+ {}
123
145
  end
124
146
  end
125
147
  end
@@ -6,13 +6,14 @@ module TRMNLP
6
6
  module Lint
7
7
  module Checks
8
8
  # Reports custom fields declared in .trmnlp.yml that never appear in the
9
- # polling settings or the markup — one finding per unused field.
9
+ # polling settings, the markup or the serverless transform — one finding
10
+ # per unused field.
10
11
  class CustomFieldsUsed < Check
11
12
  SETTINGS_KEYS = %w[polling_url polling_headers polling_body].freeze
12
13
 
13
14
  def issues
14
15
  source.custom_field_values.keys.reject { |keyname| used?(keyname) }.map do |keyname|
15
- { message: "Custom field '#{keyname}' is not used in form fields or markup." }
16
+ { message: "Custom field '#{keyname}' is not used in form fields, markup or the transform." }
16
17
  end
17
18
  end
18
19
 
@@ -20,7 +21,8 @@ module TRMNLP
20
21
 
21
22
  def used?(keyname)
22
23
  pattern = /#{Regexp.escape(keyname)}/
23
- searchable_settings.match?(pattern) || source.all_markup.match?(pattern)
24
+ searchable_settings.match?(pattern) || source.all_markup.match?(pattern) ||
25
+ source.transform_code.match?(pattern)
24
26
  end
25
27
 
26
28
  def searchable_settings
@@ -33,6 +33,15 @@ module TRMNLP
33
33
  @all_markup ||= view_markup.values.join + shared_markup
34
34
  end
35
35
 
36
+ # The serverless transform (src/transform.{py,rb,php,js}), or '' without one.
37
+ # It reads custom fields from its input, so a field may be used only there.
38
+ def transform_code
39
+ @transform_code ||= begin
40
+ path, = paths.transform_file
41
+ path ? read(path) : ''
42
+ end
43
+ end
44
+
36
45
  private
37
46
 
38
47
  attr_reader :config, :paths
@@ -39,9 +39,8 @@ module TRMNLP
39
39
  )
40
40
  end
41
41
 
42
- # A PKCE public client has no secret, so its client_id must travel in the
43
- # request body; a confidential client authenticates with HTTP Basic.
44
- def auth_scheme = provider.client_secret ? :basic_auth : :request_body
42
+ # Same rule as the hosted service: HTTP Basic only when asked for, otherwise the body.
43
+ def auth_scheme = provider.token_request_auth_method == 'header' ? :basic_auth : :request_body
45
44
 
46
45
  # oauth2 posts tokens through a connection built from the site, so give it
47
46
  # the endpoint's origin even though the full URL is absolute.
@@ -19,6 +19,7 @@ module TRMNLP
19
19
  def refresh_url = settings['oauth_refresh_url'] || token_url
20
20
  def scopes = settings['oauth_scopes']
21
21
  def scope_separator = settings['oauth_scope_separator'] || ' '
22
+ def token_request_auth_method = settings['oauth_token_request_auth_method']
22
23
  def pkce? = truthy?(settings['oauth_pkce_enabled'])
23
24
  def enabled? = truthy?(settings['oauth_enabled'])
24
25
  def client_id = env_first(ENV_CLIENT_ID, settings['oauth_client_id'])
@@ -40,11 +40,19 @@ module TRMNLP
40
40
  def run(path, inferred_language, data)
41
41
  language = config.plugin.serverless_language || inferred_language
42
42
  result = client.execute(code: path.read, stdin: JSON.generate(data), language:)
43
+ report_printed_output(result)
43
44
  return record_failure(result, data) unless result.success?
44
45
 
45
46
  parse_output(result.output, data)
46
47
  end
47
48
 
49
+ # The transform's return value travels separately, so stdout holds only what it printed.
50
+ def report_printed_output(result)
51
+ { 'stdout' => result.stdout, 'stderr' => result.stderr }.each do |stream, text|
52
+ reporter.info("transform #{stream}: #{text.strip}") unless text.strip.empty?
53
+ end
54
+ end
55
+
48
56
  def record_failure(result, fallback)
49
57
  @error = result.error || "transform exited #{result.exit_code}: #{result.stderr.strip}"
50
58
  reporter.info("transform failed: #{@error}")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TRMNLP
4
- VERSION = '0.13.2'
4
+ VERSION = '0.14.0'
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.13.2
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rockwell Schrock