wisco 0.3.6 → 0.3.8

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: dc712af30ea2647f51bf72fac2bcf59f57105efcd8382d831afcadceee912f5c
4
- data.tar.gz: b49f08a071f353521d09469452e8508e31b78f4108545b6e723083e816ecc67d
3
+ metadata.gz: a6489b0aaac5c9626f27141afebc7b4dda9b087c2cf94fee0744b9b603de4953
4
+ data.tar.gz: 298e1469cd688e9d824fd091d32ea84526934637b96e92a4ea19243dfb715e49
5
5
  SHA512:
6
- metadata.gz: 4029281c6717dc8eeef9b9b3d86018998c5846dea82d5703d10229b84bf990c8beb6d8c73928315804878b8c9ab6a5bc541b9e46b34ddef7d601c2590e354331
7
- data.tar.gz: 7a4161111ce339ffe212cacdd08365f9464b79d02a729b492e04925ed2d2a9f10687fdcd79d0eef0fcadc321cf37c9a5fb05eca57f63f3d7da747b3438204e14
6
+ metadata.gz: 4b711094e3bf5990c6a591a9a75356660c86d813a76df331f101c8cf48ecbf3d98edcf9f4ede28c4c3cff1448f19991ef89383a1fecb5c78c8a21a3b1a1ce88c
7
+ data.tar.gz: 1581e25abeb992cb6eee659407561df32a47ecbe2cacacf9bb5ea55650eabe27015114c97cf6d5f5bcbba4acbf5593416c1bb5b37ca612f682b7095ba3708ccb
@@ -1,16 +1,18 @@
1
1
  require 'fileutils'
2
+ require 'tempfile'
2
3
  require 'workato/connector/sdk'
3
4
  require 'workato/cli/exec_command'
4
5
  require_relative '../config'
5
6
  require_relative '../connector'
6
7
  require_relative '../path_utils'
8
+ require_relative '../exec_script'
7
9
 
8
10
  module Wisco
9
11
  module Commands
10
12
  module Exec
11
13
  module_function
12
14
 
13
- def run(path_arg, target_dir, input: nil, pagination: true, verbose: true, debug: false,
15
+ def run(path_arg, target_dir, input: nil, pagination: true, verbose: false, debug: false,
14
16
  extended: true, closure: nil, config_fields: nil, continue: nil,
15
17
  extended_input_schema: nil, extended_output_schema: nil)
16
18
  target_dir = File.expand_path(target_dir)
@@ -54,7 +56,7 @@ module Wisco
54
56
  next
55
57
  end
56
58
 
57
- input_files = resolve_input_files(input, fixtures_dir)
59
+ input_files = resolve_input_files(input, fixtures_dir, debug: debug)
58
60
 
59
61
  if input_files.empty?
60
62
  if %w[pick_lists methods].include?(section)
@@ -71,19 +73,32 @@ module Wisco
71
73
  end
72
74
 
73
75
  input_files.each do |input_file|
74
- execute_one(section, key, input_file, fixtures_dir,
75
- connector_full_path, connection, pagination: pagination, verbose: verbose,
76
- extended: extended, closure: closure, config_fields: config_fields,
77
- continue: continue, extended_input_schema: extended_input_schema,
78
- extended_output_schema: extended_output_schema, debug: debug)
76
+ if File.extname(input_file).downcase == '.rb'
77
+ execute_ruby_script(section, key, input_file, fixtures_dir, target_dir,
78
+ connector_full_path, connection, config,
79
+ pagination: pagination, verbose: verbose,
80
+ extended: extended, closure: closure, config_fields: config_fields,
81
+ continue: continue, extended_input_schema: extended_input_schema,
82
+ extended_output_schema: extended_output_schema, debug: debug)
83
+ else
84
+ execute_one(section, key, input_file, fixtures_dir,
85
+ connector_full_path, connection, pagination: pagination, verbose: verbose,
86
+ extended: extended, closure: closure, config_fields: config_fields,
87
+ continue: continue, extended_input_schema: extended_input_schema,
88
+ extended_output_schema: extended_output_schema, debug: debug)
89
+ end
79
90
  end
80
91
  end
81
92
  end
82
93
 
83
94
  # Resolve the list of input files to execute.
84
95
  # If an explicit input filename/path is given, use that (relative to fixtures_dir).
85
- # Otherwise glob execute_* in fixtures_dir and exclude files still containing the sentinel.
86
- def resolve_input_files(input, fixtures_dir)
96
+ # Otherwise glob execute_*.{json,rb} in fixtures_dir and exclude files still containing
97
+ # their respective sentinel.
98
+ #
99
+ # In debug mode, prints which candidate files were found and which were
100
+ # skipped (because they still contain a sentinel comment).
101
+ def resolve_input_files(input, fixtures_dir, debug: false)
87
102
  if input
88
103
  path = File.absolute_path?(input) ? input : File.join(fixtures_dir, input)
89
104
  unless File.exist?(path)
@@ -92,13 +107,28 @@ module Wisco
92
107
  end
93
108
  [path]
94
109
  else
95
- Dir.glob(File.join(fixtures_dir, 'execute_*')).select do |f|
96
- File.file?(f) && !file_has_sentinel?(f)
110
+ candidates = Dir.glob(File.join(fixtures_dir, 'execute_*.{json,rb}')).select { |f| File.file?(f) }
111
+ warn "[exec] candidate files: #{candidates.map { |f| File.basename(f) }}" if debug
112
+ candidates.reject do |f|
113
+ if file_has_sentinel?(f)
114
+ warn "[exec] skipped (sentinel): #{File.basename(f)}" if debug
115
+ true
116
+ else
117
+ false
118
+ end
97
119
  end
98
120
  end
99
121
  end
100
122
 
123
+ # Returns true if the file should be skipped. Each input format has its own
124
+ # sentinel convention:
125
+ # .json — first line exactly equals Wisco::Commands::Fixtures::SENTINEL
126
+ # .rb — first non-blank line matches `# WISCO_SKIP`
101
127
  def file_has_sentinel?(path)
128
+ if File.extname(path).downcase == '.rb'
129
+ return Wisco::ExecScript.sentinel?(path)
130
+ end
131
+
102
132
  first_line = begin
103
133
  File.open(path, &:readline).chomp
104
134
  rescue StandardError
@@ -114,7 +144,7 @@ module Wisco
114
144
  File.dirname(value) == '.' ? File.join(fixtures_dir, value) : value
115
145
  end
116
146
 
117
- def run_test(target_dir, connector_full_path, connection, verbose: true, debug: false)
147
+ def run_test(target_dir, connector_full_path, connection, verbose: false, debug: false)
118
148
  puts "Testing connection"
119
149
  fixtures_dir = File.join(target_dir, 'fixtures', 'connection', 'test')
120
150
  FileUtils.mkdir_p(fixtures_dir)
@@ -152,7 +182,7 @@ module Wisco
152
182
  end
153
183
 
154
184
  def execute_one(section, key, input_file, fixtures_dir, connector_full_path, connection,
155
- pagination: true, verbose: true, debug: false,
185
+ pagination: true, verbose: false, debug: false,
156
186
  extended: true, closure: nil, config_fields: nil, continue: nil,
157
187
  extended_input_schema: nil, extended_output_schema: nil)
158
188
  stem = input_file ? File.basename(input_file, '.*') : 'execute'
@@ -172,7 +202,11 @@ module Wisco
172
202
  options[:connection] = connection if connection
173
203
  options[:verbose] = verbose
174
204
  if use_args
175
- options[:args] = input_file if input_file
205
+ if input_file && section == 'methods'
206
+ options[:args] = normalize_method_args(input_file)
207
+ elsif input_file
208
+ options[:args] = input_file
209
+ end
176
210
  else
177
211
  options[:input] = input_file
178
212
  end
@@ -223,6 +257,7 @@ module Wisco
223
257
  cmd = Workato::CLI::ExecCommand.new(path: exec_path, options: options)
224
258
  cmd.call
225
259
  rescue StandardError => e
260
+ FileUtils.rm_f(output_file)
226
261
  File.write(error_file, "#{e.class}: #{e.message}\n\n#{e.backtrace.join("\n")}\n")
227
262
  Wisco::TerminalOutput.emit_error("Error executing #{section}.#{key} with #{input_file ? File.basename(input_file) : 'no input'}: #{e.message}")
228
263
  Wisco::TerminalOutput.emit_error(" Details written to: #{error_file}")
@@ -237,6 +272,148 @@ module Wisco
237
272
  File.write(output_file, pretty + "\n")
238
273
  puts " Written: #{output_file}"
239
274
  end
275
+
276
+ # Handles `execute_*.rb` scripts: evaluates the script to produce dynamic
277
+ # input, writes it to <subdir>/input.json, runs the connector item via
278
+ # ExecCommand, and writes <subdir>/output.json or <subdir>/error.txt.
279
+ # The subdirectory is named after the script (without .rb), e.g.
280
+ # execute_input.rb -> execute_input/.
281
+ def execute_ruby_script(section, key, script_path, fixtures_dir, target_dir,
282
+ connector_full_path, connection, config,
283
+ pagination: true, verbose: false, debug: false,
284
+ extended: true, closure: nil, config_fields: nil, continue: nil,
285
+ extended_input_schema: nil, extended_output_schema: nil)
286
+ subdir = File.join(fixtures_dir, File.basename(script_path, '.rb'))
287
+ FileUtils.mkdir_p(subdir)
288
+ input_file = File.join(subdir, 'input.json')
289
+ output_file = File.join(subdir, 'output.json')
290
+ error_file = File.join(subdir, 'error.txt')
291
+
292
+ # Step 1: evaluate the script to obtain dynamic input
293
+ connection_name = config['connection'] || 'default'
294
+
295
+ begin
296
+ generated = Wisco::ExecScript.evaluate(
297
+ script_path: script_path,
298
+ connector_full_path: connector_full_path,
299
+ target_dir: target_dir,
300
+ connection_name: connection_name
301
+ )
302
+ rescue StandardError => e
303
+ FileUtils.rm_f(input_file)
304
+ FileUtils.rm_f(output_file)
305
+ File.write(error_file, "#{e.class}: #{e.message}\n\n#{Array(e.backtrace).join("\n")}\n")
306
+ Wisco::TerminalOutput.emit_error("Error evaluating #{File.basename(script_path)}: #{e.message}")
307
+ Wisco::TerminalOutput.emit_error(" Details written to: #{error_file}")
308
+ return
309
+ end
310
+
311
+ File.write(input_file, JSON.pretty_generate(generated) + "\n")
312
+ puts " Generated: #{input_file}"
313
+
314
+ # Step 2: build ExecCommand options (mirrors execute_one)
315
+ use_args = %w[pick_lists methods].include?(section)
316
+ exec_path = if use_args
317
+ "#{section}.#{key}"
318
+ elsif section == 'triggers'
319
+ pagination ? "#{section}.#{key}.poll" : "#{section}.#{key}.poll_page"
320
+ else
321
+ "#{section}.#{key}.execute"
322
+ end
323
+
324
+ options = { connector: connector_full_path, output: output_file }
325
+ options[:connection] = connection if connection
326
+ options[:verbose] = verbose
327
+ if use_args
328
+ options[:args] = section == 'methods' ? normalize_ruby_method_args(input_file) : input_file
329
+ else
330
+ options[:input] = input_file
331
+ end
332
+
333
+ options[:closure] = resolve_option_path(closure, fixtures_dir) if closure
334
+ options[:config_fields] = resolve_option_path(config_fields, fixtures_dir) if config_fields
335
+ options[:continue] = resolve_option_path(continue, fixtures_dir) if continue
336
+
337
+ # extended schema files still live at the item's fixtures_dir (not the subdir)
338
+ unless use_args
339
+ eis = if extended_input_schema
340
+ resolve_option_path(extended_input_schema, fixtures_dir)
341
+ elsif extended
342
+ f = File.join(fixtures_dir, 'input_fields.json')
343
+ File.exist?(f) ? f : nil
344
+ end
345
+ options[:extended_input_schema] = eis if eis
346
+
347
+ eos = if extended_output_schema
348
+ resolve_option_path(extended_output_schema, fixtures_dir)
349
+ elsif extended
350
+ f = File.join(fixtures_dir, 'output_fields.json')
351
+ File.exist?(f) ? f : nil
352
+ end
353
+ options[:extended_output_schema] = eos if eos
354
+ end
355
+
356
+ if debug
357
+ warn "[exec.rb] script: #{script_path}"
358
+ warn "[exec.rb] subdir: #{subdir}"
359
+ warn "[exec.rb] path: #{exec_path}"
360
+ warn "[exec.rb] connector: #{connector_full_path}"
361
+ warn "[exec.rb] connection: #{connection.inspect}"
362
+ warn "[exec.rb] #{use_args ? 'args' : 'input'}: #{input_file}"
363
+ warn "[exec.rb] output: #{output_file}"
364
+ end
365
+
366
+ # Step 3: invoke ExecCommand against the generated input
367
+ begin
368
+ cmd = Workato::CLI::ExecCommand.new(path: exec_path, options: options)
369
+ cmd.call
370
+ rescue StandardError => e
371
+ FileUtils.rm_f(input_file)
372
+ FileUtils.rm_f(output_file)
373
+ File.write(error_file, "#{e.class}: #{e.message}\n\n#{e.backtrace.join("\n")}\n")
374
+ Wisco::TerminalOutput.emit_error("Error executing #{section}.#{key} with #{File.basename(script_path)}: #{e.message}")
375
+ Wisco::TerminalOutput.emit_error(" Details written to: #{error_file}")
376
+ return
377
+ end
378
+
379
+ FileUtils.rm_f(error_file)
380
+
381
+ return unless File.exist?(output_file)
382
+
383
+ pretty = JSON.pretty_generate(JSON.parse(File.read(output_file)))
384
+ File.write(output_file, pretty + "\n")
385
+ puts " Written: #{output_file}"
386
+ end
387
+
388
+ # The Workato SDK's InvokePath#invoke_method applies Array.wrap(args.last).flatten(1)
389
+ # when the last parameter is *args, which incorrectly unwraps array-valued positional
390
+ # arguments one level too many. These helpers compensate by pre-wrapping Array elements
391
+ # so that flatten(1) cancels out and the method receives the correct value.
392
+
393
+ # For JSON execute_input files (positional-args array format):
394
+ # [[{h1},{h2}]] → [[[{h1},{h2}]]] so SDK flatten(1) restores [[{h1},{h2}]] → method([{h1},{h2}])
395
+ def normalize_method_args(input_file)
396
+ parsed = JSON.parse(File.read(input_file))
397
+ return input_file unless parsed.is_a?(Array) && parsed.any? { |a| a.is_a?(Array) }
398
+
399
+ fixed = parsed.map { |a| a.is_a?(Array) ? [a] : a }
400
+ tmp = Tempfile.new(['wisco_args_', '.json'])
401
+ tmp.write(JSON.generate(fixed))
402
+ tmp.close
403
+ tmp.path
404
+ end
405
+
406
+ # For Ruby-script-generated input files (direct argument value):
407
+ # [{h1},{h2}] → [[{h1},{h2}]] so SDK flatten(1) restores [{h1},{h2}] → method([{h1},{h2}])
408
+ def normalize_ruby_method_args(input_file)
409
+ parsed = JSON.parse(File.read(input_file))
410
+ return input_file unless parsed.is_a?(Array)
411
+
412
+ tmp = Tempfile.new(['wisco_args_', '.json'])
413
+ tmp.write(JSON.generate([parsed]))
414
+ tmp.close
415
+ tmp.path
416
+ end
240
417
  end
241
418
  end
242
419
  end
@@ -8,11 +8,34 @@ require_relative '../path_utils'
8
8
  module Wisco
9
9
  module Commands
10
10
  module Fixtures
11
- SENTINEL = '# Remove this comment before updating. Files that include this line will be overwritten.'
11
+ SENTINEL = '# Remove this comment before updating. Files that include this line will be overwritten.'
12
+ RB_SENTINEL = '# WISCO_SKIP'
13
+
14
+ RB_TEMPLATE = <<~RUBY
15
+ # WISCO_SKIP
16
+ # Remove the WISCO_SKIP line above once this script is ready to run.
17
+ #
18
+ # The last expression in this file becomes the input passed to the connector item.
19
+ # Helper methods available inside the script:
20
+ # call_method(:name, *args) — invoke a methods: entry on the connector
21
+ # call_pick_list(:name, *args) — invoke a pick_lists: entry on the connector
22
+ # connection — Hash of decrypted settings.yaml(.enc)
23
+ #
24
+ # Example:
25
+ # require 'securerandom'
26
+ # {
27
+ # order_number: "ORD-\#{SecureRandom.hex(4).upcase}",
28
+ # created_at: Time.now.iso8601
29
+ # }
30
+
31
+ {
32
+ # TODO: fill in fields
33
+ }
34
+ RUBY
12
35
 
13
36
  module_function
14
37
 
15
- def run(path_arg, target_dir, overwrite: false, debug: false)
38
+ def run(path_arg, target_dir, overwrite: false, ruby: false, debug: false)
16
39
  target_dir = File.expand_path(target_dir)
17
40
  config_path = Wisco.config_path(target_dir)
18
41
 
@@ -44,8 +67,10 @@ module Wisco
44
67
 
45
68
  if section == 'pick_lists'
46
69
  process_pick_list(key, connector, fixtures_dir, overwrite: overwrite)
70
+ generate_execute_input_rb(fixtures_dir, overwrite: overwrite) if ruby
47
71
  elsif section == 'methods'
48
72
  process_method(key, connector, fixtures_dir, overwrite: overwrite)
73
+ generate_execute_input_rb(fixtures_dir, overwrite: overwrite) if ruby
49
74
  else
50
75
  # ── config_fields pre-check ──────────────────────────────────────
51
76
  item = connector[section.to_sym]&.[](key.to_sym)
@@ -76,6 +101,7 @@ module Wisco
76
101
  )
77
102
 
78
103
  generate_execute_input(input_fields_file, fixtures_dir, overwrite: overwrite, debug: debug)
104
+ generate_execute_input_rb(fixtures_dir, overwrite: overwrite) if ruby
79
105
 
80
106
  # ── output_fields ────────────────────────────────────────────────
81
107
  output_fields_file = File.join(fixtures_dir, 'output_fields.json')
@@ -133,6 +159,35 @@ module Wisco
133
159
  puts " Written: #{output_file}"
134
160
  end
135
161
 
162
+ # Writes an `execute_input.rb` template into `fixtures_dir`. The template
163
+ # is prefixed with `# WISCO_SKIP` so it is identifiable as unedited and
164
+ # will be skipped by `wisco exec` until the user removes that line.
165
+ #
166
+ # Overwrite rules mirror generate_execute_input:
167
+ # - File absent -> write
168
+ # - File present, # WISCO_SKIP on L1 -> overwrite (still a template)
169
+ # - File present, no sentinel -> skip (user-edited); force with --overwrite
170
+ def generate_execute_input_rb(fixtures_dir, overwrite: false)
171
+ output_file = File.join(fixtures_dir, 'execute_input.rb')
172
+
173
+ if File.exist?(output_file)
174
+ first_line = begin
175
+ File.open(output_file, &:readline).chomp
176
+ rescue StandardError
177
+ ''
178
+ end
179
+ has_sentinel = first_line.strip == RB_SENTINEL
180
+
181
+ unless has_sentinel || overwrite
182
+ puts " Skipped (user-edited): #{output_file}"
183
+ return
184
+ end
185
+ end
186
+
187
+ File.write(output_file, RB_TEMPLATE)
188
+ puts " Written: #{output_file}"
189
+ end
190
+
136
191
  # Recursively converts a Workato schema array into a template hash.
137
192
  # Scalars become "<type_value_required|optional>" placeholder strings.
138
193
  # Objects expand into a nested hash via their properties.
@@ -0,0 +1,160 @@
1
+ require 'workato/connector/sdk'
2
+ require_relative 'terminal_output'
3
+
4
+ module Wisco
5
+ # Evaluates an `execute_*.rb` script and returns the value of its last
6
+ # expression. The script is eval'd inside a binding that exposes helpers
7
+ # (`call_method`, `call_pick_list`, `connection`) so the script can build
8
+ # dynamic input that varies per run.
9
+ module ExecScript
10
+ SENTINEL_REGEX = /\A\s*#\s*WISCO_SKIP\b/.freeze
11
+
12
+ module_function
13
+
14
+ # Returns true if the file's first non-blank line is a `# WISCO_SKIP`
15
+ # comment, in which case the script should be skipped by `wisco exec`.
16
+ def sentinel?(path)
17
+ File.foreach(path) do |line|
18
+ stripped = line.strip
19
+ next if stripped.empty?
20
+ return SENTINEL_REGEX.match?(line)
21
+ end
22
+ false
23
+ rescue StandardError
24
+ false
25
+ end
26
+
27
+ # Evaluates the script at `script_path` and returns the value of its last
28
+ # expression. Raises StandardError subclasses on:
29
+ # - syntax / runtime errors in the script
30
+ # - invalid return value (must be a Hash or Array)
31
+ #
32
+ # `connector_full_path` — absolute path to connector.rb (used to build
33
+ # the SDK Connector for helper invocations).
34
+ # `target_dir` — connector project root (used to find
35
+ # settings.yaml(.enc) + master.key).
36
+ # `connection_name` — connection key inside settings.yaml; defaults to 'default'.
37
+ def evaluate(script_path:, connector_full_path:, target_dir:, connection_name: 'default')
38
+ host = ScriptHost.new(connector_full_path, target_dir, connection_name)
39
+ source = File.read(script_path)
40
+ result = host.instance_eval(source, script_path, 1)
41
+
42
+ unless result.is_a?(Hash) || result.is_a?(Array)
43
+ raise InvalidReturn,
44
+ "Script must return a Hash (for actions/triggers) or Array/Hash (for methods/pick_lists). " \
45
+ "Got: #{result.class}"
46
+ end
47
+
48
+ result
49
+ end
50
+
51
+ class InvalidReturn < StandardError; end
52
+
53
+ # Host object that the script is eval'd against. Exposes the helper API.
54
+ # Defined as a class (not a Module) so `instance_eval` gives the script
55
+ # access to standard top-level Ruby plus our helpers, without polluting
56
+ # Object globally.
57
+ class ScriptHost
58
+ def initialize(connector_full_path, target_dir, connection_name)
59
+ @connector_full_path = connector_full_path
60
+ @target_dir = target_dir
61
+ @connection_name = connection_name || 'default'
62
+ @sdk_connector = nil
63
+ @connection_loaded = false
64
+ @connection_cached = nil
65
+ @warned_settings = false
66
+ end
67
+
68
+ # Hash of decrypted settings for the configured connection. Returns {}
69
+ # and emits a one-time warning if settings can't be loaded.
70
+ def connection
71
+ return @connection_cached if @connection_loaded
72
+
73
+ @connection_loaded = true
74
+ @connection_cached = load_connection
75
+ end
76
+
77
+ # Invoke a connector `methods:` entry. Args are forwarded to the lambda
78
+ # as-is. Returns the lambda's return value.
79
+ #
80
+ # Example:
81
+ # call_method(:format_timestamp, Time.now)
82
+ def call_method(name, *args)
83
+ proxy = sdk_connector.methods
84
+ unless proxy.respond_to?(name.to_sym)
85
+ raise InvalidReturn, "No methods entry named '#{name}' found in connector."
86
+ end
87
+ proxy.public_send(name.to_sym, *args)
88
+ end
89
+
90
+ # Invoke a connector `pick_lists:` entry. `args` is a Hash of named
91
+ # arguments for dependent pick lists. The SDK supplies `connection` to
92
+ # the lambda automatically.
93
+ #
94
+ # Examples:
95
+ # call_pick_list(:active_customers)
96
+ # call_pick_list(:customer_orders, customer_id: 123)
97
+ def call_pick_list(name, **args)
98
+ proxy = sdk_connector.pick_lists
99
+ unless proxy.respond_to?(name.to_sym)
100
+ raise InvalidReturn, "No pick_lists entry named '#{name}' found in connector."
101
+ end
102
+ # SDK signature: pick_list_name(settings = nil, args = {})
103
+ # Passing nil for settings tells the SDK to reuse the connector's settings.
104
+ proxy.public_send(name.to_sym, nil, args)
105
+ end
106
+
107
+ private
108
+
109
+ def sdk_connector
110
+ @sdk_connector ||= Workato::Connector::Sdk::Connector.from_file(@connector_full_path, connection)
111
+ end
112
+
113
+ # Tries (in order): settings.yaml.enc + master.key, then settings.yaml.
114
+ # Uses Workato::Connector::Sdk::Settings so encryption is handled by the
115
+ # SDK's own loader (same path it uses for `wisco exec`). Returns {} and
116
+ # warns once on any failure.
117
+ def load_connection
118
+ enc_file = File.join(@target_dir, 'settings.yaml.enc')
119
+ key_file = File.join(@target_dir, 'master.key')
120
+ yaml_file = File.join(@target_dir, 'settings.yaml')
121
+
122
+ if File.exist?(enc_file) && File.exist?(key_file)
123
+ read_settings(enc_file) do |name|
124
+ Workato::Connector::Sdk::Settings.from_encrypted_file(enc_file, key_file, name)
125
+ end
126
+ elsif File.exist?(yaml_file)
127
+ read_settings(yaml_file) do |name|
128
+ Workato::Connector::Sdk::Settings.from_file(yaml_file, name)
129
+ end
130
+ else
131
+ warn_settings("No settings.yaml or settings.yaml.enc found in #{@target_dir}")
132
+ {}
133
+ end
134
+ end
135
+
136
+ # Reads settings using a Settings.from_* loader. Tries with the configured
137
+ # connection name first; if that key isn't present at the top level
138
+ # (KeyError), falls back to loading the whole file as a flat hash.
139
+ def read_settings(path)
140
+ hash = begin
141
+ yield(@connection_name)
142
+ rescue KeyError
143
+ yield(nil)
144
+ end
145
+ return {} unless hash.respond_to?(:to_h)
146
+
147
+ hash.to_h
148
+ rescue StandardError => e
149
+ warn_settings("Failed to load settings from #{path}: #{e.message}")
150
+ {}
151
+ end
152
+
153
+ def warn_settings(msg)
154
+ return if @warned_settings
155
+ @warned_settings = true
156
+ Wisco::TerminalOutput.emit_warning("[WARN] connection helper: #{msg}. Returning {}.")
157
+ end
158
+ end
159
+ end
160
+ end
data/lib/wisco/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Wisco
2
- VERSION = '0.3.6'
2
+ VERSION = '0.3.8'
3
3
  end
data/lib/wisco.rb CHANGED
@@ -90,7 +90,7 @@ module Wisco
90
90
  option :input, type: :string, desc: 'Specific input file'
91
91
  option :pagination, type: :boolean, default: true,
92
92
  desc: 'Triggers only: true = .poll (with pagination), false = .poll_page (without)'
93
- option :verbose, type: :boolean, default: true, desc: 'Enable detailed SDK logging'
93
+ option :verbose, type: :boolean, default: false, desc: 'Enable detailed SDK logging'
94
94
  option :extended, type: :boolean, default: true,
95
95
  desc: 'Auto-pass input_fields/output_fields as extended schema (actions/triggers)'
96
96
  option :closure, type: :string, desc: 'Closure JSON file (triggers: simulate subsequent polls)'
@@ -127,12 +127,15 @@ module Wisco
127
127
  get_users auto-detect section
128
128
  DESC
129
129
  option :overwrite, type: :boolean, default: false, desc: 'Overwrite execute_input.json even if user-edited'
130
+ option :ruby, type: :boolean, default: false,
131
+ desc: 'Also scaffold an execute_input.rb template for dynamic input scripts'
130
132
  option :debug, type: :boolean, default: false, desc: 'Print ExecCommand call details'
131
133
  def fixtures(path_arg, target_dir = nil)
132
134
  Wisco::Commands::Fixtures.run(
133
135
  path_arg,
134
136
  target_dir || Dir.pwd,
135
137
  overwrite: options[:overwrite],
138
+ ruby: options[:ruby],
136
139
  debug: options[:debug]
137
140
  )
138
141
  end
@@ -146,7 +149,7 @@ module Wisco
146
149
  option :title, type: :string, desc: 'Connector title (default: from config or connector code)'
147
150
  option :notes, type: :string, desc: 'Version notes (prompted if not provided)'
148
151
  option :folder, type: :numeric, desc: 'Workato folder ID to push connector into'
149
- option :verbose, type: :boolean, default: true, desc: 'Enable detailed SDK logging'
152
+ option :verbose, type: :boolean, default: false, desc: 'Enable detailed SDK logging'
150
153
  option :debug, type: :boolean, default: false, desc: 'Show push call details'
151
154
  def push(target_dir = nil)
152
155
  Wisco::Commands::Push.run(
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wisco
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: 0.3.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - mbillington
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-06 00:00:00.000000000 Z
11
+ date: 2026-06-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -77,6 +77,7 @@ files:
77
77
  - lib/wisco/commands/schema.rb
78
78
  - lib/wisco/config.rb
79
79
  - lib/wisco/connector.rb
80
+ - lib/wisco/exec_script.rb
80
81
  - lib/wisco/path_utils.rb
81
82
  - lib/wisco/profile.rb
82
83
  - lib/wisco/terminal_output.rb