timely-app 1.3.2 → 1.3.3
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 +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/timely-app/cli.rb +45 -11
- data/lib/timely-app/client.rb +37 -5
- data/lib/timely-app/response.rb +14 -7
- data/lib/timely-app.rb +5 -4
- data/timely-app.gemspec +1 -4
- metadata +3 -45
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9d8ce8174d129b5c2273101c2c00911260f27c6ace3f2cf674920a5b7b3c20b1
|
|
4
|
+
data.tar.gz: bc5c6f0bb086d3091e820f28ea278786ee4a54562df0bc5b9e151610d7a9b49e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 20abf6c65cf3558f079ed82e1b95245a66fac3e7301bdf6f3a8039e7ad20044efce9dddc2dee3c054ba8a18dbdc25437b8de934a37784f9521ce7c30cf34a6be
|
|
7
|
+
data.tar.gz: a84b5732a9a539ee9a2133f370748ee32d2dbff90cf916dc5a2bfa08817e1f90ce53dee7acafef8e887de6a5a40de763e2b250cb7bcc9ac72662fd18d70e3069
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 1.3.3 (2026-10-29)
|
|
4
|
+
|
|
5
|
+
- Preserve HTTP error types when the server returns malformed or unexpected JSON.
|
|
6
|
+
- Keep authorization codes and response contents out of failed sign-in diagnostics.
|
|
7
|
+
- Cancel release preparation when confirmation input is closed.
|
|
8
|
+
- Exit sign-in when the authorization code prompt is closed or blank.
|
|
9
|
+
|
|
3
10
|
## 1.3.2 (2026-07-25)
|
|
4
11
|
|
|
5
12
|
- BREAKING: Require Ruby 3.4 or newer
|
data/lib/timely-app/cli.rb
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
require "securerandom"
|
|
2
|
+
require "yaml"
|
|
3
|
+
|
|
1
4
|
module TimelyApp
|
|
2
5
|
class CLI
|
|
3
6
|
attr_reader :options, :client
|
|
@@ -37,7 +40,7 @@ module TimelyApp
|
|
|
37
40
|
token = fetch_oauth_token(auth_client, client_id, client_secret, code)
|
|
38
41
|
persist_or_print_token(token)
|
|
39
42
|
rescue TimelyApp::Error => error
|
|
40
|
-
handle_auth_failure(error
|
|
43
|
+
handle_auth_failure(error)
|
|
41
44
|
end
|
|
42
45
|
|
|
43
46
|
private
|
|
@@ -57,7 +60,12 @@ module TimelyApp
|
|
|
57
60
|
puts "Visit this URL in your browser:"
|
|
58
61
|
puts auth_url
|
|
59
62
|
puts "\nEnter authorization code here:"
|
|
60
|
-
gets
|
|
63
|
+
code = gets&.chomp
|
|
64
|
+
if code.nil? || code.empty?
|
|
65
|
+
puts "Authorization code is required"
|
|
66
|
+
exit 1
|
|
67
|
+
end
|
|
68
|
+
code
|
|
61
69
|
end
|
|
62
70
|
|
|
63
71
|
def oauth_redirect_uri
|
|
@@ -84,15 +92,13 @@ module TimelyApp
|
|
|
84
92
|
else
|
|
85
93
|
puts "\nAccess token:\n#{token.access_token}\n"
|
|
86
94
|
end
|
|
87
|
-
puts "
|
|
95
|
+
puts "Authentication succeeded" if options[:verbose]
|
|
88
96
|
end
|
|
89
97
|
|
|
90
|
-
def handle_auth_failure(error
|
|
98
|
+
def handle_auth_failure(error)
|
|
91
99
|
puts "Authentication failed"
|
|
92
100
|
if options[:verbose]
|
|
93
|
-
puts "Code: #{code}"
|
|
94
101
|
puts "Response code: #{error.response.code}"
|
|
95
|
-
puts "Response: #{error.response.body}"
|
|
96
102
|
end
|
|
97
103
|
exit 1
|
|
98
104
|
end
|
|
@@ -102,9 +108,16 @@ module TimelyApp
|
|
|
102
108
|
end
|
|
103
109
|
|
|
104
110
|
def read_config_file
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
return unless File.exist?(config_file_path)
|
|
112
|
+
|
|
113
|
+
config = YAML.safe_load_file(
|
|
114
|
+
config_file_path,
|
|
115
|
+
permitted_classes: [Symbol, Time],
|
|
116
|
+
aliases: false
|
|
117
|
+
)
|
|
118
|
+
return {} unless config.is_a?(Hash)
|
|
119
|
+
|
|
120
|
+
config.to_h { |key, value| [key.to_s, value] }
|
|
108
121
|
end
|
|
109
122
|
|
|
110
123
|
def fetch_account_id
|
|
@@ -124,9 +137,30 @@ module TimelyApp
|
|
|
124
137
|
|
|
125
138
|
def save_config_file(**options)
|
|
126
139
|
config = read_config_file || {}
|
|
127
|
-
config.merge!(options)
|
|
128
|
-
|
|
140
|
+
config.merge!(options.to_h { |key, value| [key.to_s, value] })
|
|
141
|
+
write_config_file(config.to_yaml)
|
|
129
142
|
puts "Saved to #{config_file_path}"
|
|
130
143
|
end
|
|
144
|
+
|
|
145
|
+
def write_config_file(content)
|
|
146
|
+
temporary_path = "#{config_file_path}.#{Process.pid}.#{SecureRandom.hex(8)}.tmp"
|
|
147
|
+
|
|
148
|
+
File.open(temporary_path, File::WRONLY | File::CREAT | File::EXCL, 0o600) do |file|
|
|
149
|
+
file.write(content)
|
|
150
|
+
file.flush
|
|
151
|
+
file.fsync
|
|
152
|
+
end
|
|
153
|
+
File.rename(temporary_path, config_file_path)
|
|
154
|
+
File.chmod(0o600, config_file_path)
|
|
155
|
+
sync_config_directory
|
|
156
|
+
ensure
|
|
157
|
+
File.delete(temporary_path) if temporary_path && File.exist?(temporary_path)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def sync_config_directory
|
|
161
|
+
File.open(File.dirname(config_file_path), File::RDONLY, &:fsync)
|
|
162
|
+
rescue Errno::EINVAL
|
|
163
|
+
nil
|
|
164
|
+
end
|
|
131
165
|
end
|
|
132
166
|
end
|
data/lib/timely-app/client.rb
CHANGED
|
@@ -10,6 +10,11 @@ require "json"
|
|
|
10
10
|
|
|
11
11
|
module TimelyApp
|
|
12
12
|
class Client
|
|
13
|
+
DEFAULT_OPEN_TIMEOUT = 5
|
|
14
|
+
DEFAULT_READ_TIMEOUT = 30
|
|
15
|
+
FILTERED_VALUE = "[FILTERED]"
|
|
16
|
+
SENSITIVE_FIELD = /authorization|code|credential|password|secret|token/i
|
|
17
|
+
|
|
13
18
|
attr_accessor :account_id
|
|
14
19
|
|
|
15
20
|
def initialize(options = {})
|
|
@@ -18,9 +23,7 @@ module TimelyApp
|
|
|
18
23
|
@user_agent = options.fetch(:user_agent) { "timely-app/#{VERSION} ruby/#{RUBY_VERSION}" }
|
|
19
24
|
|
|
20
25
|
@host = "api.timelyapp.com"
|
|
21
|
-
|
|
22
|
-
@http = Net::HTTP.new(@host, Net::HTTP.https_default_port)
|
|
23
|
-
@http.use_ssl = true
|
|
26
|
+
@http = build_http(options)
|
|
24
27
|
|
|
25
28
|
@account_id = options[:account_id]
|
|
26
29
|
@verbose = options[:verbose] || !ENV["VERBOSE"].nil? || false
|
|
@@ -32,6 +35,14 @@ module TimelyApp
|
|
|
32
35
|
|
|
33
36
|
private
|
|
34
37
|
|
|
38
|
+
def build_http(options)
|
|
39
|
+
http = Net::HTTP.new(@host, Net::HTTP.https_default_port)
|
|
40
|
+
http.use_ssl = true
|
|
41
|
+
http.open_timeout = options.fetch(:open_timeout, DEFAULT_OPEN_TIMEOUT)
|
|
42
|
+
http.read_timeout = options.fetch(:read_timeout, DEFAULT_READ_TIMEOUT)
|
|
43
|
+
http
|
|
44
|
+
end
|
|
45
|
+
|
|
35
46
|
def verbose?
|
|
36
47
|
@verbose == true
|
|
37
48
|
end
|
|
@@ -74,8 +85,29 @@ module TimelyApp
|
|
|
74
85
|
def log_verbose_exchange(http_request, response)
|
|
75
86
|
return unless verbose?
|
|
76
87
|
|
|
77
|
-
puts ">> request: #{http_request.method} #{http_request.path} #{http_request.body}"
|
|
78
|
-
puts "<< response: #{http_request.method} #{http_request.path} #{response.code} #{response.body}"
|
|
88
|
+
puts ">> request: #{http_request.method} #{http_request.path} #{filtered_body(http_request.body)}"
|
|
89
|
+
puts "<< response: #{http_request.method} #{http_request.path} #{response.code} #{filtered_body(response.body)}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def filtered_body(body)
|
|
93
|
+
return if body.nil? || body.empty?
|
|
94
|
+
|
|
95
|
+
JSON.generate(filter_sensitive_values(JSON.parse(body)))
|
|
96
|
+
rescue JSON::ParserError
|
|
97
|
+
FILTERED_VALUE
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def filter_sensitive_values(value)
|
|
101
|
+
case value
|
|
102
|
+
when Hash
|
|
103
|
+
value.to_h { |key, nested_value|
|
|
104
|
+
[key, key.match?(SENSITIVE_FIELD) ? FILTERED_VALUE : filter_sensitive_values(nested_value)]
|
|
105
|
+
}
|
|
106
|
+
when Array
|
|
107
|
+
value.map { |nested_value| filter_sensitive_values(nested_value) }
|
|
108
|
+
else
|
|
109
|
+
value
|
|
110
|
+
end
|
|
79
111
|
end
|
|
80
112
|
end
|
|
81
113
|
end
|
data/lib/timely-app/response.rb
CHANGED
|
@@ -27,13 +27,20 @@ module TimelyApp
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def error(response)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
body = error_body(response)
|
|
31
|
+
errors = body["errors"]
|
|
32
|
+
message = errors.is_a?(Hash) ? errors["message"] : nil
|
|
33
|
+
message ||= body["error_description"]
|
|
34
|
+
error_class(response).new(message, response: response, errors: errors)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def error_body(response)
|
|
38
|
+
return {} unless response.content_type == "application/json"
|
|
39
|
+
|
|
40
|
+
body = JSON.parse(response.body.to_s)
|
|
41
|
+
body.is_a?(Hash) ? body : {}
|
|
42
|
+
rescue JSON::ParserError
|
|
43
|
+
{}
|
|
37
44
|
end
|
|
38
45
|
|
|
39
46
|
def error_class(object)
|
data/lib/timely-app.rb
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
module TimelyApp
|
|
2
|
+
VERSION = "1.3.3"
|
|
3
|
+
end
|
|
4
|
+
|
|
1
5
|
require "timely-app/client"
|
|
2
6
|
require "timely-app/client/accounts"
|
|
3
7
|
require "timely-app/client/auth"
|
|
@@ -13,7 +17,4 @@ require "timely-app/client/teams"
|
|
|
13
17
|
require "timely-app/client/user_capacities"
|
|
14
18
|
require "timely-app/client/users"
|
|
15
19
|
require "timely-app/client/webhooks"
|
|
16
|
-
|
|
17
|
-
module TimelyApp
|
|
18
|
-
VERSION = "1.3.2"
|
|
19
|
-
end
|
|
20
|
+
require "timely-app/cli"
|
data/timely-app.gemspec
CHANGED
|
@@ -30,9 +30,6 @@ Gem::Specification.new do |gem|
|
|
|
30
30
|
gem.add_development_dependency "rspec-core", "~> 3"
|
|
31
31
|
gem.add_development_dependency "polyrun", ">= 2.2.0"
|
|
32
32
|
gem.add_development_dependency "rspec-expectations", "~> 3"
|
|
33
|
-
gem.add_development_dependency "rspec_junit_formatter", "~> 0.6"
|
|
34
|
-
gem.add_development_dependency "simplecov", "~> 0.22"
|
|
35
|
-
gem.add_development_dependency "simplecov-cobertura", "~> 3"
|
|
36
33
|
gem.add_development_dependency "webmock", "~> 3"
|
|
37
34
|
gem.add_development_dependency "pry", "~> 0.15"
|
|
38
35
|
gem.add_development_dependency "standard", "~> 1.52"
|
|
@@ -41,6 +38,6 @@ Gem::Specification.new do |gem|
|
|
|
41
38
|
gem.add_development_dependency "standard-rspec", "~> 0.3"
|
|
42
39
|
gem.add_development_dependency "rubocop-rspec", "~> 3.8"
|
|
43
40
|
gem.add_development_dependency "rubocop-thread_safety", "~> 0.7"
|
|
44
|
-
gem.add_development_dependency "rbs", "~>
|
|
41
|
+
gem.add_development_dependency "rbs", "~> 4"
|
|
45
42
|
gem.add_development_dependency "appraisal", "~> 2"
|
|
46
43
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: timely-app
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -51,48 +51,6 @@ dependencies:
|
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '3'
|
|
54
|
-
- !ruby/object:Gem::Dependency
|
|
55
|
-
name: rspec_junit_formatter
|
|
56
|
-
requirement: !ruby/object:Gem::Requirement
|
|
57
|
-
requirements:
|
|
58
|
-
- - "~>"
|
|
59
|
-
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0.6'
|
|
61
|
-
type: :development
|
|
62
|
-
prerelease: false
|
|
63
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
-
requirements:
|
|
65
|
-
- - "~>"
|
|
66
|
-
- !ruby/object:Gem::Version
|
|
67
|
-
version: '0.6'
|
|
68
|
-
- !ruby/object:Gem::Dependency
|
|
69
|
-
name: simplecov
|
|
70
|
-
requirement: !ruby/object:Gem::Requirement
|
|
71
|
-
requirements:
|
|
72
|
-
- - "~>"
|
|
73
|
-
- !ruby/object:Gem::Version
|
|
74
|
-
version: '0.22'
|
|
75
|
-
type: :development
|
|
76
|
-
prerelease: false
|
|
77
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
-
requirements:
|
|
79
|
-
- - "~>"
|
|
80
|
-
- !ruby/object:Gem::Version
|
|
81
|
-
version: '0.22'
|
|
82
|
-
- !ruby/object:Gem::Dependency
|
|
83
|
-
name: simplecov-cobertura
|
|
84
|
-
requirement: !ruby/object:Gem::Requirement
|
|
85
|
-
requirements:
|
|
86
|
-
- - "~>"
|
|
87
|
-
- !ruby/object:Gem::Version
|
|
88
|
-
version: '3'
|
|
89
|
-
type: :development
|
|
90
|
-
prerelease: false
|
|
91
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
-
requirements:
|
|
93
|
-
- - "~>"
|
|
94
|
-
- !ruby/object:Gem::Version
|
|
95
|
-
version: '3'
|
|
96
54
|
- !ruby/object:Gem::Dependency
|
|
97
55
|
name: webmock
|
|
98
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -211,14 +169,14 @@ dependencies:
|
|
|
211
169
|
requirements:
|
|
212
170
|
- - "~>"
|
|
213
171
|
- !ruby/object:Gem::Version
|
|
214
|
-
version: '
|
|
172
|
+
version: '4'
|
|
215
173
|
type: :development
|
|
216
174
|
prerelease: false
|
|
217
175
|
version_requirements: !ruby/object:Gem::Requirement
|
|
218
176
|
requirements:
|
|
219
177
|
- - "~>"
|
|
220
178
|
- !ruby/object:Gem::Version
|
|
221
|
-
version: '
|
|
179
|
+
version: '4'
|
|
222
180
|
- !ruby/object:Gem::Dependency
|
|
223
181
|
name: appraisal
|
|
224
182
|
requirement: !ruby/object:Gem::Requirement
|