wisco 0.1.7
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 +7 -0
- data/bin/wisco +7 -0
- data/lib/wisco/commands/exec.rb +126 -0
- data/lib/wisco/commands/fixtures.rb +149 -0
- data/lib/wisco/commands/init.rb +63 -0
- data/lib/wisco/commands/list.rb +192 -0
- data/lib/wisco/commands/pull.rb +176 -0
- data/lib/wisco/commands/push.rb +129 -0
- data/lib/wisco/config.rb +43 -0
- data/lib/wisco/connector.rb +91 -0
- data/lib/wisco/path_utils.rb +57 -0
- data/lib/wisco/version.rb +3 -0
- data/lib/wisco/workato_api.rb +75 -0
- data/lib/wisco.rb +162 -0
- metadata +98 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'base64'
|
|
3
|
+
require 'json'
|
|
4
|
+
require_relative '../config'
|
|
5
|
+
require_relative '../connector'
|
|
6
|
+
require_relative '../workato_api'
|
|
7
|
+
|
|
8
|
+
module Wisco
|
|
9
|
+
module Commands
|
|
10
|
+
module Pull
|
|
11
|
+
VALID_WHAT = %w[all logo code versions meta].freeze
|
|
12
|
+
LOGO_FILENAME = 'logo.png'.freeze
|
|
13
|
+
VERSION_KEYS = %w[latest_version latest_version_note latest_released_version
|
|
14
|
+
latest_released_version_note latest_shared_version
|
|
15
|
+
latest_shared_version_note oem_shared_version
|
|
16
|
+
oem_shared_at recent_released_versions].freeze
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def run(target_dir, what:, title:, debug:)
|
|
21
|
+
target_dir = File.expand_path(target_dir)
|
|
22
|
+
config_path = Wisco.config_path(target_dir)
|
|
23
|
+
|
|
24
|
+
unless File.exist?(config_path)
|
|
25
|
+
warn "Error: No #{Wisco::WISCO_DIR}/#{Wisco::CONFIG_FILENAME} found in #{target_dir}."
|
|
26
|
+
warn " Run '#{Wisco::CLI_NAME} init' first."
|
|
27
|
+
exit 1
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
config = Wisco::Config.load_config(config_path)
|
|
31
|
+
config = Wisco::Config.ensure_api_config(config, config_path)
|
|
32
|
+
what_list = parse_what(what)
|
|
33
|
+
title ||= derive_title(target_dir)
|
|
34
|
+
|
|
35
|
+
pull_dir = File.join(target_dir, Wisco::WISCO_DIR, 'pull')
|
|
36
|
+
FileUtils.mkdir_p(pull_dir)
|
|
37
|
+
|
|
38
|
+
api = Wisco::WorkatoApi.new(
|
|
39
|
+
hostname: config.dig('workato_developer_api', 'hostname'),
|
|
40
|
+
api_token: config.dig('workato_developer_api', 'api_token'),
|
|
41
|
+
debug: debug
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
datetime = Time.now.localtime.strftime('%Y%m%d_%H%M%S')
|
|
45
|
+
|
|
46
|
+
# Step 1: Search
|
|
47
|
+
warn "[pull] Searching for connector: #{title}" if debug
|
|
48
|
+
status, search_data, raw_body = api.search_connector(title: title)
|
|
49
|
+
handle_http_error(status, 'search', title, body: raw_body)
|
|
50
|
+
|
|
51
|
+
connector = extract_single(search_data, title)
|
|
52
|
+
maybe_save_title(connector, config, config_path)
|
|
53
|
+
search_file = File.join(pull_dir, 'meta.json')
|
|
54
|
+
write_json(search_file, search_data)
|
|
55
|
+
puts "Meta: \t#{search_file}"
|
|
56
|
+
|
|
57
|
+
# Logo
|
|
58
|
+
if what_list.include?('logo') || what_list.include?('all')
|
|
59
|
+
connector_root = config.dig('connector', 'path')
|
|
60
|
+
root_logo = File.join(connector_root, LOGO_FILENAME)
|
|
61
|
+
if File.exist?(root_logo)
|
|
62
|
+
logo_file = save_logo(pull_dir, connector['logo'])
|
|
63
|
+
puts "Logo: \t#{logo_file}" if logo_file
|
|
64
|
+
puts " (saved to pull dir — #{LOGO_FILENAME} already exists in connector root)"
|
|
65
|
+
else
|
|
66
|
+
logo_file = save_logo(connector_root, connector['logo'])
|
|
67
|
+
puts "Logo: \t#{logo_file}" if logo_file
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Versions
|
|
72
|
+
if what_list.include?('versions') || what_list.include?('all')
|
|
73
|
+
versions_file = save_versions(pull_dir, connector)
|
|
74
|
+
puts "Versions: \t#{versions_file}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Code
|
|
78
|
+
if what_list.include?('code') || what_list.include?('all')
|
|
79
|
+
warn "[pull] Fetching code for id=#{connector['id']}" if debug
|
|
80
|
+
status, code_data, raw_body = api.get_connector_code(id: connector['id'])
|
|
81
|
+
handle_http_error(status, 'code', connector['id'], body: raw_body)
|
|
82
|
+
|
|
83
|
+
rb_file = File.join(pull_dir, "#{connector['name']}.rb")
|
|
84
|
+
File.write(rb_file, code_data.dig('data', 'code').to_s)
|
|
85
|
+
puts "Code: \t#{rb_file}"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# ── helpers ──────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
def derive_title(target_dir)
|
|
92
|
+
connector = Wisco::Connector.load_connector_from_config(target_dir)
|
|
93
|
+
title = connector[:title]
|
|
94
|
+
if title.nil? || title.strip.empty?
|
|
95
|
+
warn "Error: Could not derive connector title from connector file."
|
|
96
|
+
warn " Use --title to specify it explicitly."
|
|
97
|
+
exit 1
|
|
98
|
+
end
|
|
99
|
+
title
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def parse_what(what_str)
|
|
103
|
+
values = what_str.to_s.split(',').map(&:strip).map(&:downcase)
|
|
104
|
+
invalid = values - VALID_WHAT
|
|
105
|
+
unless invalid.empty?
|
|
106
|
+
warn "Error: Invalid --what value(s): #{invalid.join(', ')}"
|
|
107
|
+
warn " Valid values: #{VALID_WHAT.join(', ')}"
|
|
108
|
+
exit 1
|
|
109
|
+
end
|
|
110
|
+
values
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def extract_single(data, title)
|
|
114
|
+
results = case data
|
|
115
|
+
when Hash then Array(data['data'] || data)
|
|
116
|
+
when Array then data
|
|
117
|
+
else []
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
if results.empty?
|
|
121
|
+
warn "Error: No connector found matching '#{title}'."
|
|
122
|
+
warn " Check the title and try again, or use --title with a different value."
|
|
123
|
+
exit 1
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
if results.size > 1
|
|
127
|
+
warn "Error: Multiple connectors matched '#{title}':"
|
|
128
|
+
results.each { |r| warn " - #{r['title']} (id: #{r['id']}, name: #{r['name']})" }
|
|
129
|
+
warn " Use --title with a more specific name."
|
|
130
|
+
exit 1
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
results.first
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def handle_http_error(status, context, identifier, body: nil)
|
|
137
|
+
return if status == 200
|
|
138
|
+
|
|
139
|
+
warn "Error: #{context} request failed for '#{identifier}' (HTTP #{status})."
|
|
140
|
+
warn " Response body: #{body}" unless body.nil? || body.strip.empty?
|
|
141
|
+
exit 1
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def save_logo(pull_dir, base64_logo)
|
|
145
|
+
return nil if base64_logo.nil? || base64_logo.strip.empty?
|
|
146
|
+
|
|
147
|
+
logo_file = File.join(pull_dir, LOGO_FILENAME)
|
|
148
|
+
File.binwrite(logo_file, Base64.decode64(base64_logo))
|
|
149
|
+
logo_file
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def save_versions(pull_dir, connector)
|
|
153
|
+
versions = connector.select { |k, _| VERSION_KEYS.include?(k) }
|
|
154
|
+
versions_file = File.join(pull_dir, 'versions.json')
|
|
155
|
+
write_json(versions_file, versions)
|
|
156
|
+
versions_file
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def maybe_save_title(connector, config, config_path)
|
|
160
|
+
stored = config.dig('connector', 'title')
|
|
161
|
+
return unless stored.nil? || stored.strip.empty?
|
|
162
|
+
|
|
163
|
+
api_title = connector['title']
|
|
164
|
+
return unless api_title && !api_title.strip.empty?
|
|
165
|
+
|
|
166
|
+
config['connector'] ||= {}
|
|
167
|
+
config['connector']['title'] = api_title
|
|
168
|
+
Wisco::Config.save_config(config_path, config)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def write_json(path, data)
|
|
172
|
+
File.write(path, JSON.pretty_generate(data) + "\n")
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
require 'workato/cli/push_command'
|
|
2
|
+
require_relative '../config'
|
|
3
|
+
require_relative '../connector'
|
|
4
|
+
|
|
5
|
+
module Wisco
|
|
6
|
+
module Commands
|
|
7
|
+
module Push
|
|
8
|
+
REQUIRED_ASSETS = [
|
|
9
|
+
->(connector_path, connector_file) { File.join(connector_path, connector_file) },
|
|
10
|
+
->(connector_path, _) { File.join(connector_path, 'logo.png') },
|
|
11
|
+
->(connector_path, _) { File.join(connector_path, 'README.md') }
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def run(target_dir, title:, notes:, folder:, verbose:, debug:)
|
|
17
|
+
target_dir = File.expand_path(target_dir)
|
|
18
|
+
config_path = Wisco.config_path(target_dir)
|
|
19
|
+
|
|
20
|
+
unless File.exist?(config_path)
|
|
21
|
+
warn "Error: No #{Wisco::WISCO_DIR}/#{Wisco::CONFIG_FILENAME} found in #{target_dir}."
|
|
22
|
+
warn " Run '#{Wisco::CLI_NAME} init' first."
|
|
23
|
+
exit 1
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
config = Wisco::Config.load_config(config_path)
|
|
27
|
+
config = Wisco::Config.ensure_api_config(config, config_path)
|
|
28
|
+
|
|
29
|
+
connector_path = config.dig('connector', 'path')
|
|
30
|
+
connector_file = config.dig('connector', 'file')
|
|
31
|
+
|
|
32
|
+
if connector_path.nil? || connector_file.nil?
|
|
33
|
+
warn "Error: #{Wisco::WISCO_DIR}/#{Wisco::CONFIG_FILENAME} is missing connector path/file. Run '#{Wisco::CLI_NAME} init' again."
|
|
34
|
+
exit 1
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Check all required assets exist before attempting the push
|
|
38
|
+
check_assets(connector_path, connector_file)
|
|
39
|
+
|
|
40
|
+
# Resolve title — save to config if obtained from option or prompt
|
|
41
|
+
title = resolve_title(title, config, config_path, target_dir)
|
|
42
|
+
|
|
43
|
+
# Resolve notes — prompt if not provided via --notes
|
|
44
|
+
notes = resolve_notes(notes)
|
|
45
|
+
|
|
46
|
+
options = {
|
|
47
|
+
environment: "https://#{config.dig('workato_developer_api', 'hostname')}",
|
|
48
|
+
api_token: config.dig('workato_developer_api', 'api_token'),
|
|
49
|
+
connector: connector_file,
|
|
50
|
+
title: title,
|
|
51
|
+
notes: notes,
|
|
52
|
+
verbose: verbose
|
|
53
|
+
}
|
|
54
|
+
options[:folder] = folder if folder
|
|
55
|
+
|
|
56
|
+
if debug
|
|
57
|
+
warn "[push] environment: #{options[:environment]}"
|
|
58
|
+
warn "[push] connector: #{options[:connector]}"
|
|
59
|
+
warn "[push] title: #{options[:title]}"
|
|
60
|
+
warn "[push] notes: #{options[:notes]}"
|
|
61
|
+
warn "[push] folder: #{folder.inspect}"
|
|
62
|
+
warn "[push] verbose: #{verbose}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
Dir.chdir(connector_path) do
|
|
66
|
+
Workato::CLI::PushCommand.new(options: options).call
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# ── helpers ──────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
def check_assets(connector_path, connector_file)
|
|
73
|
+
missing = REQUIRED_ASSETS
|
|
74
|
+
.map { |fn| fn.call(connector_path, connector_file) }
|
|
75
|
+
.reject { |path| File.exist?(path) }
|
|
76
|
+
|
|
77
|
+
return if missing.empty?
|
|
78
|
+
|
|
79
|
+
missing.each { |path| warn "Error: Required asset not found: #{path}" }
|
|
80
|
+
exit 1
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def resolve_title(title, config, config_path, target_dir)
|
|
84
|
+
# 1. Explicit --title option
|
|
85
|
+
if title
|
|
86
|
+
save_title(title, config, config_path)
|
|
87
|
+
return title
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# 2. Stored in config
|
|
91
|
+
stored = config.dig('connector', 'title')
|
|
92
|
+
return stored if stored && !stored.strip.empty?
|
|
93
|
+
|
|
94
|
+
# 3. From connector code
|
|
95
|
+
connector = Wisco::Connector.load_connector_from_config(target_dir)
|
|
96
|
+
code_title = connector[:title]
|
|
97
|
+
return code_title if code_title && !code_title.strip.empty?
|
|
98
|
+
|
|
99
|
+
# 4. Prompt user
|
|
100
|
+
print 'Connector title not found. Enter a title: '
|
|
101
|
+
title = $stdin.gets.strip
|
|
102
|
+
if title.empty?
|
|
103
|
+
warn 'Error: A connector title is required.'
|
|
104
|
+
exit 1
|
|
105
|
+
end
|
|
106
|
+
save_title(title, config, config_path)
|
|
107
|
+
title
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def save_title(title, config, config_path)
|
|
111
|
+
config['connector'] ||= {}
|
|
112
|
+
config['connector']['title'] = title
|
|
113
|
+
Wisco::Config.save_config(config_path, config)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def resolve_notes(notes)
|
|
117
|
+
return notes if notes && !notes.strip.empty?
|
|
118
|
+
|
|
119
|
+
loop do
|
|
120
|
+
print 'Version notes (required): '
|
|
121
|
+
value = $stdin.gets.strip
|
|
122
|
+
return value unless value.empty?
|
|
123
|
+
|
|
124
|
+
warn 'Version notes cannot be blank.'
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
data/lib/wisco/config.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Wisco
|
|
2
|
+
module Config
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
# Ensures workato_developer_api hostname and api_token are present in config.
|
|
6
|
+
# Prompts the user for any missing values and saves the updated config.
|
|
7
|
+
def ensure_api_config(config, config_path)
|
|
8
|
+
api_cfg = config['workato_developer_api'] ||= {}
|
|
9
|
+
|
|
10
|
+
if api_cfg['hostname'].nil? || api_cfg['hostname'].strip.empty?
|
|
11
|
+
print 'Workato API hostname not configured. Enter hostname (e.g. app.au.workato.com): '
|
|
12
|
+
api_cfg['hostname'] = $stdin.gets.strip
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if api_cfg['api_token'].nil? || api_cfg['api_token'].strip.empty?
|
|
16
|
+
print 'Workato API token not configured. Enter your API token: '
|
|
17
|
+
api_cfg['api_token'] = $stdin.gets.strip
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
save_config(config_path, config)
|
|
21
|
+
config
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def load_config(path)
|
|
25
|
+
return {} unless File.exist?(path)
|
|
26
|
+
|
|
27
|
+
begin
|
|
28
|
+
JSON.parse(File.read(path))
|
|
29
|
+
rescue JSON::ParserError => e
|
|
30
|
+
warn "Warning: Existing #{Wisco::WISCO_DIR}/#{Wisco::CONFIG_FILENAME} contains invalid JSON (#{e.message})."
|
|
31
|
+
warn ' Connector section will be overwritten; other content may be lost.'
|
|
32
|
+
{}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def save_config(path, config)
|
|
37
|
+
File.write(path, JSON.pretty_generate(config) + "\n")
|
|
38
|
+
rescue SystemCallError => e
|
|
39
|
+
warn "Error: Could not write #{Wisco::WISCO_DIR}/#{Wisco::CONFIG_FILENAME}: #{e.message}"
|
|
40
|
+
exit 1
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require_relative 'config'
|
|
2
|
+
|
|
3
|
+
module Wisco
|
|
4
|
+
module Connector
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def detect_connector(dir)
|
|
8
|
+
candidates = candidate_files(dir)
|
|
9
|
+
|
|
10
|
+
if candidates.empty?
|
|
11
|
+
warn " No .rb files found in #{dir}"
|
|
12
|
+
return nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
candidates.each do |filename|
|
|
16
|
+
puts " Checking #{filename}..."
|
|
17
|
+
return filename if valid_connector?(File.join(dir, filename))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def load_connector_from_config(target_dir)
|
|
24
|
+
target_dir = File.expand_path(target_dir)
|
|
25
|
+
config_path = Wisco.config_path(target_dir)
|
|
26
|
+
|
|
27
|
+
unless File.exist?(config_path)
|
|
28
|
+
warn "Error: No #{Wisco::WISCO_DIR}/#{Wisco::CONFIG_FILENAME} found in #{target_dir}."
|
|
29
|
+
warn " Run '#{Wisco::CLI_NAME} init' first."
|
|
30
|
+
exit 1
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
config = Wisco::Config.load_config(config_path)
|
|
34
|
+
connector_file = config.dig('connector', 'file')
|
|
35
|
+
connector_path = config.dig('connector', 'path')
|
|
36
|
+
|
|
37
|
+
if connector_file.nil? || connector_path.nil?
|
|
38
|
+
warn "Error: #{Wisco::WISCO_DIR}/#{Wisco::CONFIG_FILENAME} is missing connector path/file. Run '#{Wisco::CLI_NAME} init' again."
|
|
39
|
+
exit 1
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
full_path = File.join(connector_path, connector_file)
|
|
43
|
+
unless File.exist?(full_path)
|
|
44
|
+
warn "Error: Connector file not found: #{full_path}"
|
|
45
|
+
exit 1
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
code = File.read(full_path)
|
|
49
|
+
original_verbose = $VERBOSE
|
|
50
|
+
$VERBOSE = nil
|
|
51
|
+
begin
|
|
52
|
+
result = eval(code) # rubocop:disable Security/Eval
|
|
53
|
+
rescue Exception => e
|
|
54
|
+
raise "\nConnector contains errors:\n#{e.message}"
|
|
55
|
+
warn "Error: Failed to load connector: #{e.message}"
|
|
56
|
+
exit 1
|
|
57
|
+
ensure
|
|
58
|
+
$VERBOSE = original_verbose
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
result
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def candidate_files(dir)
|
|
65
|
+
primary = 'connector.rb'
|
|
66
|
+
others = Dir.glob(File.join(dir, '*.rb'))
|
|
67
|
+
.map { |f| File.basename(f) }
|
|
68
|
+
.reject { |f| f == primary }
|
|
69
|
+
.sort
|
|
70
|
+
|
|
71
|
+
result = []
|
|
72
|
+
result << primary if File.exist?(File.join(dir, primary))
|
|
73
|
+
result.concat(others)
|
|
74
|
+
result
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def valid_connector?(path)
|
|
78
|
+
code = File.read(path)
|
|
79
|
+
original_verbose = $VERBOSE
|
|
80
|
+
$VERBOSE = nil
|
|
81
|
+
begin
|
|
82
|
+
result = eval(code) # rubocop:disable Security/Eval
|
|
83
|
+
result.is_a?(Hash) && result.key?(:title)
|
|
84
|
+
rescue Exception => e
|
|
85
|
+
false
|
|
86
|
+
ensure
|
|
87
|
+
$VERBOSE = original_verbose
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Wisco
|
|
2
|
+
module PathUtils
|
|
3
|
+
VALID_SECTIONS = %w[actions triggers].freeze
|
|
4
|
+
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
# Returns an array of [section, key] pairs derived from path_arg.
|
|
8
|
+
#
|
|
9
|
+
# Accepted forms:
|
|
10
|
+
# "section.key" — one specific key in a known section
|
|
11
|
+
# "section" — all keys in that section
|
|
12
|
+
# "key" — auto-detect section; error if found in both or neither
|
|
13
|
+
def parse_path(path_arg, connector)
|
|
14
|
+
parts = path_arg.split('.', 2)
|
|
15
|
+
|
|
16
|
+
if parts.size == 2
|
|
17
|
+
section, key = parts
|
|
18
|
+
unless VALID_SECTIONS.include?(section)
|
|
19
|
+
warn "Error: Invalid section '#{section}'. Valid sections: #{VALID_SECTIONS.join(', ')}."
|
|
20
|
+
exit 1
|
|
21
|
+
end
|
|
22
|
+
items = connector[section.to_sym]
|
|
23
|
+
unless items&.key?(key.to_sym)
|
|
24
|
+
warn "Error: '#{key}' not found in #{section}."
|
|
25
|
+
exit 1
|
|
26
|
+
end
|
|
27
|
+
[[section, key]]
|
|
28
|
+
|
|
29
|
+
elsif VALID_SECTIONS.include?(path_arg)
|
|
30
|
+
section = path_arg
|
|
31
|
+
items = connector[section.to_sym]
|
|
32
|
+
if items.nil? || items.empty?
|
|
33
|
+
warn "Error: No keys found in #{section}."
|
|
34
|
+
exit 1
|
|
35
|
+
end
|
|
36
|
+
items.keys.map { |k| [section, k.to_s] }
|
|
37
|
+
|
|
38
|
+
else
|
|
39
|
+
key = path_arg
|
|
40
|
+
in_actions = connector[:actions]&.key?(key.to_sym) || false
|
|
41
|
+
in_triggers = connector[:triggers]&.key?(key.to_sym) || false
|
|
42
|
+
|
|
43
|
+
case [in_actions, in_triggers]
|
|
44
|
+
when [true, false] then [['actions', key]]
|
|
45
|
+
when [false, true] then [['triggers', key]]
|
|
46
|
+
when [true, true]
|
|
47
|
+
warn "Error: '#{key}' exists in both actions and triggers."
|
|
48
|
+
warn " Qualify with section, e.g. 'actions.#{key}'."
|
|
49
|
+
exit 1
|
|
50
|
+
else
|
|
51
|
+
warn "Error: '#{key}' not found in actions or triggers."
|
|
52
|
+
exit 1
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'uri'
|
|
3
|
+
require 'cgi'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Wisco
|
|
7
|
+
class WorkatoApi
|
|
8
|
+
def initialize(hostname:, api_token:, debug: false)
|
|
9
|
+
@hostname = hostname
|
|
10
|
+
@api_token = api_token
|
|
11
|
+
@debug = debug
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Returns [status_int, parsed_hash_or_nil, raw_body_string]
|
|
15
|
+
def search_connector(title:)
|
|
16
|
+
request(:get, '/api/custom_connectors/search', params: { title: title })
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_connector_code(id:)
|
|
20
|
+
request(:get, "/api/custom_connectors/#{id}/code")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def request(_method, path, params: nil, body: nil)
|
|
26
|
+
# Use URI only for host/port; build path+query as a raw string so
|
|
27
|
+
# Net::HTTP sends it without any re-encoding by the URI class.
|
|
28
|
+
base_uri = URI("https://#{@hostname}")
|
|
29
|
+
query = params.map { |k, v| "#{k}=#{v.to_s.gsub(' ', '%20')}" }.join('&') if params
|
|
30
|
+
request_path = query ? "#{path}?#{query}" : path
|
|
31
|
+
|
|
32
|
+
req = Net::HTTP::Get.new(request_path)
|
|
33
|
+
req['Authorization'] = "Bearer #{@api_token}"
|
|
34
|
+
req['Accept'] = '*/*'
|
|
35
|
+
req['User-Agent'] = 'wisco'
|
|
36
|
+
if body
|
|
37
|
+
req['Content-Type'] = 'application/json'
|
|
38
|
+
req.body = body.to_json
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if @debug
|
|
42
|
+
warn "[api] → GET https://#{@hostname}#{request_path}"
|
|
43
|
+
warn "[api] Authorization: Bearer #{masked_token}"
|
|
44
|
+
warn "[api] Accept: */*"
|
|
45
|
+
warn "[api] User-Agent: wisco"
|
|
46
|
+
warn "[api] Content-Type: application/json" if body
|
|
47
|
+
warn "[api] Body: #{req.body}" if body
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
http = Net::HTTP.new(base_uri.host, base_uri.port)
|
|
51
|
+
http.use_ssl = true
|
|
52
|
+
resp = http.request(req)
|
|
53
|
+
raw_body = resp.body.to_s
|
|
54
|
+
|
|
55
|
+
if @debug
|
|
56
|
+
warn "[api] ← #{resp.code} #{resp.message}"
|
|
57
|
+
warn "[api] Body: #{raw_body}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
parsed = JSON.parse(raw_body)
|
|
62
|
+
rescue JSON::ParserError
|
|
63
|
+
parsed = nil
|
|
64
|
+
end
|
|
65
|
+
[resp.code.to_i, parsed, raw_body]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def masked_token
|
|
69
|
+
return '(empty)' if @api_token.nil? || @api_token.empty?
|
|
70
|
+
|
|
71
|
+
visible = [@api_token.length, 6].min
|
|
72
|
+
"#{@api_token[0, visible]}..."
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|