neetodeploy 1.1.24 → 1.1.25

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: b75e5cad096f5e2a0d819736b207332962853eb7528e39252f796f693a4b3aba
4
- data.tar.gz: 5b919d398d1e36f981b2644914d43677448d90313911ce9a43243e312cf83467
3
+ metadata.gz: a0396bbc473e87998286bdc95905c601721c24787da18a64e7ab67a9cd42ddfa
4
+ data.tar.gz: 8d002597268fcca2205aa42a5fcdeb371f2a9edcbdfeb14ff80bff449407b7e0
5
5
  SHA512:
6
- metadata.gz: b724ca4ea923f1be277dc7feb878968699b883f5548410a9668a57fbcbf7db410a215dc3112fb6697c248f24f4dcec0d6810ebdbc6d258c2e78cadc5399c203b
7
- data.tar.gz: c1e39a69f4085f5f31950b6530d5f4faed4351cf49b945024a1a2406ec16d666d9102e7bc039b4319eacd5d0ec5ab76782be511af7c6bc08d304a61ff34055ba
6
+ metadata.gz: 0f07f591022176db4bd74ab0b6a5c3839634c728f5db28cef1cd504d2ff728fcec857fe84d85a9c02ba9b01a99a56d1086d41c023a318d789b5f3b26ddcd32b0
7
+ data.tar.gz: d7bc9e789cf2174cca1beb3fc37b816ceeb7d554fab45be4d50e46d24d0f4cec7d4f1d094b78f5b22847f46c59bf65c125317fa7180c307cfb202a07d8e5286b
data/Gemfile.lock CHANGED
@@ -1,13 +1,16 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- neetodeploy (1.1.24)
5
- aws-sdk-cloudwatchlogs
6
- aws-sdk-cognitoidentity
4
+ neetodeploy (1.1.25)
5
+ base64
6
+ bigdecimal
7
7
  colorize
8
+ csv
8
9
  dotenv (~> 2.8.1)
9
10
  httparty (~> 0.21.0)
10
11
  launchy (~> 2.5.0)
12
+ logger
13
+ ostruct
11
14
  rexml
12
15
  terminal-table (~> 3.0.2)
13
16
  thor (~> 1.5.0)
@@ -34,8 +37,11 @@ GEM
34
37
  jmespath (~> 1, >= 1.6.1)
35
38
  aws-sigv4 (1.5.2)
36
39
  aws-eventstream (~> 1, >= 1.0.2)
40
+ base64 (0.3.0)
41
+ bigdecimal (4.0.1)
37
42
  byebug (11.1.3)
38
43
  colorize (1.1.0)
44
+ csv (3.3.5)
39
45
  dotenv (2.8.1)
40
46
  eventmachine (1.2.7)
41
47
  httparty (0.21.0)
@@ -44,8 +50,10 @@ GEM
44
50
  jmespath (1.6.2)
45
51
  launchy (2.5.2)
46
52
  addressable (~> 2.8)
53
+ logger (1.7.0)
47
54
  mini_mime (1.1.2)
48
55
  multi_xml (0.6.0)
56
+ ostruct (0.6.3)
49
57
  public_suffix (5.0.1)
50
58
  rexml (3.4.1)
51
59
  terminal-table (3.0.2)
@@ -69,6 +77,7 @@ PLATFORMS
69
77
  arm64-darwin-21
70
78
  arm64-darwin-23
71
79
  arm64-darwin-24
80
+ arm64-darwin-25
72
81
  x86_64-darwin-21
73
82
  x86_64-darwin-23
74
83
  x86_64-linux
@@ -9,6 +9,10 @@ module NeetoDeploy
9
9
  def addon_url(addon_name)
10
10
  "#{NEETO_DEPLOY_CLI_API_BASE_URL}/addons/#{addon_name}"
11
11
  end
12
+
13
+ def scheduled_exports_url(app_slug)
14
+ "#{NEETO_DEPLOY_CLI_API_BASE_URL}/scheduled_exports/#{app_slug}"
15
+ end
12
16
  end
13
17
  end
14
18
  end
@@ -42,7 +42,7 @@ module NeetoDeploy
42
42
  end
43
43
 
44
44
  def print_output
45
- ui.error(@response["error"]) and return unless @response.success?
45
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
46
46
 
47
47
  flatten_hash_from(JSON[@response.body]).each do |k, v|
48
48
  ui.info("#{k}: #{v}")
@@ -12,34 +12,32 @@ module NeetoDeploy
12
12
  include Constants
13
13
  include Session
14
14
 
15
- attr_reader :app_name
15
+ attr_reader :app_slug
16
16
 
17
17
  def initialize(options)
18
18
  super()
19
- @app_name = options[:app]
19
+ @app_slug = options[:app]
20
20
  end
21
21
 
22
22
  def run
23
23
  ui.execute_with_loading("Fetching info...") do
24
- send_request
24
+ @response = send_get_request(scheduled_exports_url(app_slug), { app_slug: })
25
25
  end
26
26
  print_output
27
27
  end
28
28
 
29
29
  private
30
30
 
31
- def send_request
32
- @response = send_get_request("#{NEETO_DEPLOY_CLI_API_BASE_URL}/scheduled_exports/#{app_name}", {app_slug: app_name})
33
- end
34
-
35
31
  def print_output
36
- ui.error(@response["error"]) and return unless @response.success?
32
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
37
33
 
38
34
  scheduled_exports = JSON.parse(@response.body)["scheduled_exports_enabled"]
39
35
  if scheduled_exports.nil?
40
36
  ui.error("App doesn't seem to have a primary database addon")
37
+ elsif scheduled_exports
38
+ ui.success("Scheduled exports is turned on for #{app_slug}'s primary database")
41
39
  else
42
- ui.info("Scheduled exports is turned #{scheduled_exports ? "\u001b[32mon\u001b[0m" : "\u001b[31moff\u001b[0m"} for #{app_name}'s primary database")
40
+ ui.info("Scheduled exports is turned off for #{app_slug}'s primary database")
43
41
  end
44
42
  end
45
43
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "thor"
4
4
 
5
+ require_relative "./constants"
5
6
  require_relative "./list"
6
7
 
7
8
  module NeetoDeploy
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NeetoDeploy
4
+ class CLI
5
+ module AutoscalingConfig
6
+ module Constants
7
+ def autoscaling_configs_url(app_slug)
8
+ "#{NEETO_DEPLOY_CLI_API_BASE_URL}/autoscaling_configs/#{app_slug}"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,12 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
+ require "terminal-table"
5
+
6
+ require_relative "../session"
7
+ require_relative "./constants"
4
8
 
5
9
  module NeetoDeploy
6
10
  class CLI
7
11
  module AutoscalingConfig
8
12
  class List < CLI::Base
9
13
  include Session
14
+ include Constants
10
15
 
11
16
  attr_reader :app_slug
12
17
 
@@ -16,15 +21,22 @@ module NeetoDeploy
16
21
  end
17
22
 
18
23
  def run
19
- response = send_get_request(
20
- "#{NEETO_DEPLOY_CLI_API_BASE_URL}/autoscaling_configs/#{app_slug}", {
21
- app_slug:
22
- }
23
- )
24
+ ui.execute_with_loading("Fetching autoscaling configs...") do
25
+ @response = send_get_request(autoscaling_configs_url(app_slug), { app_slug: })
26
+ end
27
+
28
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
29
+
30
+ status = JSON.parse(@response.body)["autoscaling_status"]
24
31
 
25
- ui.error(response) and return unless response.success?
32
+ if status.nil? || status.empty?
33
+ ui.info("No autoscaling configs found for #{app_slug}.")
34
+ return
35
+ end
26
36
 
27
- ui.success(JSON.parse(response.body)["autoscaling_status"])
37
+ rows = status.map { |process_type, config| [process_type, config&.[]("enabled") ? "Yes" : "No", config&.[]("type") || "-"] }
38
+ table = Terminal::Table.new(headings: ["Process Type", "Enabled", "Type"], rows: rows)
39
+ ui.info(table)
28
40
  end
29
41
  end
30
42
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "thor"
4
4
 
5
+ require_relative "./constants"
5
6
  require_relative "./list"
6
7
 
7
8
  module NeetoDeploy
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NeetoDeploy
4
+ class CLI
5
+ module Certificates
6
+ module Constants
7
+ def certificates_url
8
+ "#{NEETO_DEPLOY_CLI_API_BASE_URL}/certificates"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,12 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
+ require "terminal-table"
5
+
6
+ require_relative "../session"
7
+ require_relative "./constants"
4
8
 
5
9
  module NeetoDeploy
6
10
  class CLI
7
11
  module Certificates
8
12
  class List < CLI::Base
9
13
  include Session
14
+ include Constants
10
15
 
11
16
  attr_reader :app_slug
12
17
 
@@ -16,15 +21,27 @@ module NeetoDeploy
16
21
  end
17
22
 
18
23
  def run
19
- response = send_get_request(
20
- "#{NEETO_DEPLOY_CLI_API_BASE_URL}/certificates", {
21
- app_slug:
22
- }
23
- )
24
+ ui.execute_with_loading("Fetching certificates...") do
25
+ @response = send_get_request(certificates_url, { app_slug: })
26
+ end
27
+
28
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
29
+
30
+ certificates = JSON.parse(@response.body)["certificates"]
31
+
32
+ if certificates.nil? || certificates.empty?
33
+ ui.info("No certificates found for #{app_slug}.")
34
+ return
35
+ end
24
36
 
25
- ui.error(response) and return unless response.success?
37
+ rows = certificates.map do |cert|
38
+ domains = Array(cert["domains"]).map { |d| d["hostname"] }.join(", ")
39
+ expiring_soon = cert["expires_before_30_days"] ? " (expiring soon)" : ""
40
+ [cert["name"], cert["expiration"], domains + expiring_soon]
41
+ end
26
42
 
27
- ui.success(JSON.parse(response.body)["certificates"])
43
+ table = Terminal::Table.new(headings: ["Name", "Expiration", "Domains"], rows: rows)
44
+ ui.info(table)
28
45
  end
29
46
  end
30
47
  end
@@ -15,21 +15,39 @@ module NeetoDeploy
15
15
  def process!
16
16
  start_spinner
17
17
  send_console_session_request
18
- ui.error("\n#{@response.body}") and return unless @response.success?
18
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
19
19
 
20
+ print_connection_banner
20
21
  start_console
22
+ print_session_end
21
23
  connection_cleanup_callback
22
24
  end
23
25
 
24
26
  private
25
27
 
26
28
  def console_executable_path
27
- gem_spec = Gem::Specification.find_by_name("neetodeploy")
28
- gem_dir = gem_spec.gem_dir
29
- executable_path = File.join(gem_dir, "exe", console_executable_name)
29
+ if ENV["DEV_ENVIRONMENT"]
30
+ File.expand_path("../../../exe/#{console_executable_name}", __dir__)
31
+ else
32
+ gem_spec = Gem::Specification.find_by_name("neetodeploy")
33
+ File.join(gem_spec.gem_dir, "exe", console_executable_name)
34
+ end
35
+ end
36
+
37
+ def print_connection_banner
38
+ separator = "─" * 60
39
+ ui.success("Connected to #{addon_name}. Type \"exit\" to end the session.")
40
+ ui.info(separator)
41
+ end
42
+
43
+ def print_session_end
44
+ separator = "─" * 60
45
+ ui.info(separator)
46
+ ui.info("Session ended.")
30
47
  end
31
48
 
32
49
  def start_console
50
+ @pubsub_token = @response.parsed_response["console_pubsub_token"]
33
51
  console_access_token = @response.parsed_response["console_access_token"]
34
52
  pod_name = @response.parsed_response["pod_name"]
35
53
  database_url = @response.parsed_response["database_url"]
@@ -39,7 +57,7 @@ module NeetoDeploy
39
57
  end
40
58
 
41
59
  def execute_console(pod_name, console_access_token, container_name, kind, database_url)
42
- system("#{console_executable_path} -podname #{pod_name} -token #{console_access_token} -kind #{kind} -container #{container_name} -url #{database_url}")
60
+ system(console_executable_path, "-podname", pod_name, "-token", console_access_token, "-kind", kind, "-container", container_name, "-url", database_url, err: File::NULL)
43
61
  end
44
62
 
45
63
  def send_console_session_request
@@ -49,11 +67,12 @@ module NeetoDeploy
49
67
 
50
68
  def connection_cleanup_callback
51
69
  url = "#{console_session_base_url}/#{addon_name}"
52
- send_delete_request(url, { pubsub_token: @pubsub_token })
70
+ response = send_delete_request(url, { pubsub_token: @pubsub_token })
71
+ ui.error("Warning: failed to clean up session — #{response["error"] || response.message}") unless response.success?
53
72
  end
54
73
 
55
74
  def start_spinner
56
- @spinner = TTY::Spinner.new("Setting up dyno [:spinner]", format: :classic)
75
+ @spinner = TTY::Spinner.new("Setting up console for #{addon_name} [:spinner]", format: :classic)
57
76
  @spinner.auto_spin
58
77
  end
59
78
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "terminal-table"
4
4
  require "thor"
5
+ require "io/console"
5
6
 
6
7
  require_relative "../session"
7
8
  require_relative "./constants"
@@ -22,16 +23,14 @@ module NeetoDeploy
22
23
  end
23
24
 
24
25
  def run
25
- response = send_get_request(
26
- environment_variables_url, {
27
- app_slug:
28
- }
29
- )
26
+ ui.execute_with_loading("Fetching environment variables...") do
27
+ @response = send_get_request(environment_variables_url, { app_slug: })
28
+ end
30
29
 
31
- ui.error(response) and return unless response.success?
30
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
32
31
 
33
- data = is_json_format ? json_data(response["environment_variables"]) : table_data(response["environment_variables"])
34
- ui.success(data)
32
+ data = is_json_format ? json_data(@response["environment_variables"]) : table_data(@response["environment_variables"])
33
+ ui.info(data)
35
34
  end
36
35
 
37
36
  private
@@ -47,7 +46,20 @@ module NeetoDeploy
47
46
  end
48
47
 
49
48
  def table_data(envs)
50
- Terminal::Table.new(headings: table_columns, rows: table_rows(envs))
49
+ rows = table_rows(envs)
50
+ key_width = rows.map { |r| r[0].length }.max || 10
51
+ val_width = [terminal_width - key_width - 7, 20].max
52
+ wrapped_rows = rows.map { |key, value| [key, wrap_value(value, val_width)] }
53
+ Terminal::Table.new(headings: table_columns, rows: wrapped_rows, style: { all_separators: false })
54
+ end
55
+
56
+ def terminal_width
57
+ IO.console&.winsize&.last || 120
58
+ end
59
+
60
+ def wrap_value(text, width)
61
+ return text if text.length <= width
62
+ text.chars.each_slice(width).map(&:join).join("\n")
51
63
  end
52
64
 
53
65
  def json_data(envs)
@@ -25,24 +25,11 @@ module NeetoDeploy
25
25
  end
26
26
 
27
27
  def run
28
- table = Terminal::Table.new(
29
- headings: table_columns,
30
- rows: environment_variables.map { |environment_variable|
31
- [environment_variable[:key], environment_variable[:value]]
32
- }
33
- )
34
- ui.info(table)
35
-
36
- ui.info("Setting environment variables and restarting app...")
37
-
38
- response = send_post_request(
39
- environment_variables_url, {
40
- app_slug:,
41
- environment_variables:
42
- }
43
- )
44
-
45
- ui.error(response) and return unless response.success?
28
+ ui.execute_with_loading("Setting environment variables and restarting app...") do
29
+ @response = send_post_request(environment_variables_url, { app_slug:, environment_variables: })
30
+ end
31
+
32
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
46
33
 
47
34
  ui.success("Done")
48
35
  end
@@ -25,16 +25,11 @@ module NeetoDeploy
25
25
  end
26
26
 
27
27
  def run
28
- ui.info("Unsetting environment variable keys #{environment_variables_string_array} and restarting app...")
29
-
30
- response = send_delete_request(
31
- environment_variables_url, {
32
- app_slug:,
33
- environment_variables:
34
- }
35
- )
28
+ ui.execute_with_loading("Unsetting #{environment_variables_string_array.join(", ")} and restarting app...") do
29
+ @response = send_delete_request(environment_variables_url, { app_slug:, environment_variables: })
30
+ end
36
31
 
37
- ui.error(response) and return unless response.success?
32
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
38
33
 
39
34
  ui.success("Done")
40
35
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
- require "readline"
5
4
  require "rbconfig"
6
5
 
7
6
  require_relative "../session"
@@ -27,18 +26,35 @@ module NeetoDeploy
27
26
  def process!
28
27
  start_spinner
29
28
  send_console_session_request
30
- ui.error("\n#{@response.body}") and return unless @response.success?
29
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
31
30
 
31
+ print_connection_banner
32
32
  start_console
33
+ print_session_end
33
34
  connection_cleanup_callback
34
35
  end
35
36
 
36
37
  private
37
38
 
38
39
  def console_executable_path
39
- gem_spec = Gem::Specification.find_by_name("neetodeploy")
40
- gem_dir = gem_spec.gem_dir
41
- executable_path = File.join(gem_dir, "exe", console_executable_name)
40
+ if ENV["DEV_ENVIRONMENT"]
41
+ File.expand_path("../../../../exe/#{console_executable_name}", __dir__)
42
+ else
43
+ gem_spec = Gem::Specification.find_by_name("neetodeploy")
44
+ File.join(gem_spec.gem_dir, "exe", console_executable_name)
45
+ end
46
+ end
47
+
48
+ def print_connection_banner
49
+ separator = "─" * 60
50
+ ui.success("Connected to #{app_name}. Type \"exit\" to end the session.")
51
+ ui.info(separator)
52
+ end
53
+
54
+ def print_session_end
55
+ separator = "─" * 60
56
+ ui.info(separator)
57
+ ui.info("Session ended.")
42
58
  end
43
59
 
44
60
  def start_console
@@ -46,7 +62,7 @@ module NeetoDeploy
46
62
  console_access_token = @response.parsed_response["console_access_token"]
47
63
  pod_name = "#{app_name}-#{@pubsub_token}-console-deployment"
48
64
  container_name = "#{app_name}-#{@pubsub_token}-console"
49
- system("#{console_executable_path} -podname #{pod_name} -token #{console_access_token} -container #{container_name}")
65
+ system(console_executable_path, "-podname", pod_name, "-token", console_access_token, "-container", container_name, err: File::NULL)
50
66
  end
51
67
 
52
68
  def send_console_session_request
@@ -56,11 +72,12 @@ module NeetoDeploy
56
72
 
57
73
  def connection_cleanup_callback
58
74
  url = "#{console_session_base_url}/#{app_name}"
59
- send_delete_request(url, { pubsub_token: @pubsub_token })
75
+ response = send_delete_request(url, { pubsub_token: @pubsub_token })
76
+ ui.error("Warning: failed to clean up session — #{response["error"] || response.message}") unless response.success?
60
77
  end
61
78
 
62
79
  def start_spinner
63
- @spinner = TTY::Spinner.new("Setting up dyno [:spinner]", format: :classic)
80
+ @spinner = TTY::Spinner.new("Setting up console for #{app_name} [:spinner]", format: :classic)
64
81
  @spinner.auto_spin
65
82
  end
66
83
 
@@ -53,13 +53,15 @@ module NeetoDeploy
53
53
 
54
54
  def wait_until_user_authenticates!
55
55
  two_minutes_later = Time.now.utc + 120
56
- loop_running = true
57
- while loop_running
58
- loop_running = false if check_if_user_authenticated
59
- loop_running = false if Time.now.utc >= two_minutes_later
56
+ authenticated = false
57
+ while Time.now.utc < two_minutes_later
58
+ if check_if_user_authenticated
59
+ authenticated = true
60
+ break
61
+ end
60
62
  sleep LOGIN_STATUS_CHECK_INTERVAL_SECONDS
61
63
  end
62
- ui.say("Logged in successfully")
64
+ authenticated ? ui.success("Logged in successfully") : ui.error("Login timed out. Please try again.")
63
65
  end
64
66
 
65
67
  def check_if_user_authenticated
@@ -3,8 +3,6 @@
3
3
  require "thor"
4
4
  require "colorize"
5
5
 
6
- require "aws-sdk-cloudwatchlogs"
7
- require "aws-sdk-cognitoidentity"
8
6
  require_relative "../session"
9
7
  require_relative "./constants"
10
8
 
@@ -15,86 +13,97 @@ module NeetoDeploy
15
13
  include Constants
16
14
  include Session
17
15
 
16
+ POLL_INTERVAL_SECONDS = 2
17
+ INITIAL_LIMIT = 100
18
+ POLL_LIMIT = 1000
19
+
18
20
  attr_reader :app_slug, :process_type
19
21
 
20
22
  def initialize(app_slug, process_type = nil)
21
23
  super()
22
24
  @app_slug = app_slug
23
25
  @process_type = process_type
24
- @log_client = nil
26
+ @last_timestamp_ms = nil
25
27
  end
26
28
 
27
29
  def process!
28
30
  start_spinner
29
- response = authenticate_user
30
- return unless response.success?
31
+ response = fetch_logs
32
+ @spinner.stop
33
+
34
+ unless response.success?
35
+ ui.error(response["error"] || response.message)
36
+ return
37
+ end
38
+
39
+ data = JSON.parse(response.body)
40
+
41
+ unless data["configured"]
42
+ ui.error("Logs are not configured for this app.")
43
+ return
44
+ end
31
45
 
32
- log_params = JSON[response.body]
33
- @log_client = cloud_watch_client(log_params)
34
- start_log_streaming(log_params)
46
+ # Initial batch returned DESC — reverse for chronological display
47
+ logs = data["logs"].reverse
48
+ logs.each { |log| print_log(log) }
49
+ # Subtract 10s buffer to account for potential client/server clock skew
50
+ @last_timestamp_ms = logs.any? ? logs.last["timestamp"] : ((Time.now.to_f - 10) * 1000).to_i
51
+
52
+ poll_for_logs
35
53
  end
36
54
 
37
55
  private
38
56
 
39
- def authenticate_user
40
- response = send_post_request(authenticate_live_stream_url, { app_slug:, process_type: })
41
- @spinner.stop && ui.error(response) unless response.success?
42
- response
43
- end
57
+ def fetch_logs(after_timestamp_ms: nil)
58
+ params = { app_slug:, process_type:, limit: INITIAL_LIMIT }
44
59
 
45
- def cloud_watch_client(log_params)
46
- cognito = Aws::CognitoIdentity::Client.new(region: log_params["region"])
47
- identity_id = cognito.get_id(identity_pool_id: log_params["identity_pool_id"]).identity_id
48
- credentials = cognito.get_credentials_for_identity(identity_id:).credentials
49
-
50
- Aws::CloudWatchLogs::Client.new(
51
- region: log_params["region"],
52
- access_key_id: credentials.access_key_id,
53
- secret_access_key: credentials.secret_key,
54
- session_token: credentials.session_token
55
- )
56
- end
60
+ if after_timestamp_ms
61
+ params[:after_timestamp] = ms_to_iso8601(after_timestamp_ms)
62
+ params[:limit] = POLL_LIMIT
63
+ end
57
64
 
58
- def start_spinner
59
- @spinner = TTY::Spinner.new("Starting live stream session [:spinner]", format: :classic)
60
- @spinner.auto_spin
65
+ send_get_request(logs_v2_url, params)
61
66
  end
62
67
 
63
- def start_log_streaming(log_params)
64
- next_token = nil
65
- @spinner.stop
68
+ def poll_for_logs
66
69
  loop do
67
- begin
68
- params = {
69
- log_group_name: log_params["log_group_name"],
70
- log_stream_name: log_params["log_stream_name"],
71
- start_from_head: !next_token.nil?,
72
- limit: next_token.nil? ? 100 : 1000
73
- }
74
-
75
- params[:next_token] = next_token unless next_token.nil?
76
- response = @log_client.get_log_events(params)
77
-
78
- response.events.each do |event|
79
- puts "\e[35m#{Time.at(event.timestamp / 1000)}\e[0m #{event.message}"
80
- end
81
-
82
- if next_token == response.next_forward_token
83
- sleep 3
84
- else
85
- next_token = response.next_forward_token
86
- end
87
- rescue Aws::CloudWatchLogs::Errors::ExpiredTokenException,
88
- Aws::CloudWatchLogs::Errors::UnrecognizedClientException => e
89
- warn "Stream time limit has been reached"
90
- exit 1
91
- rescue => e
70
+ response = fetch_logs(after_timestamp_ms: @last_timestamp_ms)
71
+
72
+ if response.success?
73
+ data = JSON.parse(response.body)
74
+ logs = data["logs"]
75
+ logs.each { |log| print_log(log) }
76
+ @last_timestamp_ms = logs.last["timestamp"] if logs.any?
77
+ next if data["has_more_forward"]
78
+ elsif response.code.between?(400, 499)
79
+ ui.error("Error #{response.code}: #{response["error"] || response.message}")
80
+ break
81
+ else
92
82
  warn "Connection lost. Reconnecting..."
93
- sleep 5
94
83
  end
95
- sleep 2
84
+
85
+ sleep POLL_INTERVAL_SECONDS
86
+ rescue Interrupt
87
+ raise
88
+ rescue => e
89
+ warn "Error: #{e.message}. Reconnecting..."
90
+ sleep 5
96
91
  end
97
92
  end
93
+
94
+ def print_log(log)
95
+ timestamp = Time.at(log["timestamp"] / 1000.0)
96
+ puts "#{timestamp.to_s.colorize(:magenta)} #{log["message"]}"
97
+ end
98
+
99
+ def ms_to_iso8601(ms)
100
+ Time.at(ms / 1000.0).utc.iso8601(3)
101
+ end
102
+
103
+ def start_spinner
104
+ @spinner = TTY::Spinner.new("Starting live stream session [:spinner]", format: :classic)
105
+ @spinner.auto_spin
106
+ end
98
107
  end
99
108
  end
100
109
  end
@@ -4,10 +4,11 @@ module NeetoDeploy
4
4
  class CLI
5
5
  module Logs
6
6
  module Constants
7
- NEETO_DEPLOY_CLI_API_ENVIRONMENT_VARIABLES_URL = "#{NEETO_DEPLOY_CLI_API_BASE_URL}/logs".freeze
7
+ NEETO_DEPLOY_CLI_V2_API_BASE_URL = "#{NEETO_DEPLOY_HOST}/api/cli/v2".freeze
8
+ NEETO_DEPLOY_CLI_API_V2_LOGS_URL = "#{NEETO_DEPLOY_CLI_V2_API_BASE_URL}/logs".freeze
8
9
 
9
- def authenticate_live_stream_url
10
- NEETO_DEPLOY_CLI_API_ENVIRONMENT_VARIABLES_URL
10
+ def logs_v2_url
11
+ NEETO_DEPLOY_CLI_API_V2_LOGS_URL
11
12
  end
12
13
  end
13
14
  end
@@ -38,7 +38,7 @@ module NeetoDeploy
38
38
  end
39
39
 
40
40
  def print_output
41
- ui.error(@response["error"]) and return unless @response.success?
41
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
42
42
 
43
43
  ui.success(@response["message"])
44
44
  end
@@ -26,11 +26,11 @@ module NeetoDeploy
26
26
  command: "CONFIG RESETSTAT"
27
27
  }
28
28
  )
29
- ui.error(response["error"]) and return unless response.success?
29
+ ui.error(response["error"] || response.message) and return unless response.success?
30
30
 
31
31
  ui.success("Stats reset successful.")
32
32
  else
33
- puts "Reset cancelled."
33
+ ui.info("Reset cancelled.")
34
34
  end
35
35
  end
36
36
  end
@@ -23,7 +23,7 @@ module NeetoDeploy
23
23
 
24
24
  def run
25
25
  unless valid_config?
26
- ui.error("Could not set cofig \"#{key}\". Please refer manageable redis configs: #{AVAILABLE_REDIS_CONFIGS_TO_EDIT}")
26
+ ui.error("Could not set config \"#{key}\". Manageable Redis configs: #{AVAILABLE_REDIS_CONFIGS_TO_EDIT.join(", ")}")
27
27
  return
28
28
  end
29
29
 
@@ -49,7 +49,7 @@ module NeetoDeploy
49
49
  end
50
50
 
51
51
  def print_output
52
- ui.error(@response["error"]) and return unless @response.success?
52
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
53
53
 
54
54
  ui.success("#{key} config for #{addon_name} set to #{value}")
55
55
  end
@@ -50,7 +50,7 @@ module NeetoDeploy
50
50
  when /linux/
51
51
  :linux
52
52
  else
53
- raise Error::WebDriverError, "unsupported os: #{host_os.inspect}"
53
+ raise Error, "unsupported os: #{host_os.inspect}"
54
54
  end
55
55
  )
56
56
  end
@@ -45,7 +45,7 @@ module NeetoDeploy
45
45
 
46
46
  desc "logs", "Show logs"
47
47
  option :app, type: :string, aliases: "-a", required: true, desc: "App slug"
48
- option :process_type, type: :string, aliases: "-p", required: true, desc: "Process type"
48
+ option :process_type, type: :string, aliases: "-p", desc: "Process type (optional)"
49
49
  def logs
50
50
  CLI::Logs::Base.new(options[:app], options[:process_type]).process!
51
51
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NeetoDeploy
4
- VERSION = "1.1.24"
4
+ VERSION = "1.1.25"
5
5
  CLI_API_VERSION = "v1"
6
6
  end
data/neetodeploy.gemspec CHANGED
@@ -38,9 +38,13 @@ Gem::Specification.new do |spec|
38
38
  spec.add_dependency "websocket-eventmachine-client"
39
39
  spec.add_dependency "colorize"
40
40
  spec.add_dependency "tty-spinner"
41
- spec.add_dependency "aws-sdk-cloudwatchlogs"
42
- spec.add_dependency "aws-sdk-cognitoidentity"
43
41
  spec.add_dependency "rexml"
42
+ # Gems removed from Ruby stdlib in 3.4/4.0, required by our dependencies
43
+ spec.add_dependency "base64"
44
+ spec.add_dependency "bigdecimal"
45
+ spec.add_dependency "csv"
46
+ spec.add_dependency "logger"
47
+ spec.add_dependency "ostruct"
44
48
 
45
49
  # To add the files from submodules
46
50
  `git submodule --quiet foreach pwd`.split($\).each do |submodule_path|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neetodeploy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.24
4
+ version: 1.1.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Subin Siby
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-23 00:00:00.000000000 Z
11
+ date: 2026-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dotenv
@@ -123,7 +123,7 @@ dependencies:
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
- name: aws-sdk-cloudwatchlogs
126
+ name: rexml
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - ">="
@@ -137,7 +137,7 @@ dependencies:
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  - !ruby/object:Gem::Dependency
140
- name: aws-sdk-cognitoidentity
140
+ name: base64
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - ">="
@@ -151,7 +151,49 @@ dependencies:
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
153
  - !ruby/object:Gem::Dependency
154
- name: rexml
154
+ name: bigdecimal
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: csv
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: logger
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :runtime
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: ostruct
155
197
  requirement: !ruby/object:Gem::Requirement
156
198
  requirements:
157
199
  - - ">="
@@ -187,9 +229,11 @@ files:
187
229
  - lib/neeto_deploy/cli/addon/info.rb
188
230
  - lib/neeto_deploy/cli/addon/scheduled_exports_settings.rb
189
231
  - lib/neeto_deploy/cli/autoscaling_config/commands.rb
232
+ - lib/neeto_deploy/cli/autoscaling_config/constants.rb
190
233
  - lib/neeto_deploy/cli/autoscaling_config/list.rb
191
234
  - lib/neeto_deploy/cli/base.rb
192
235
  - lib/neeto_deploy/cli/certificates/commands.rb
236
+ - lib/neeto_deploy/cli/certificates/constants.rb
193
237
  - lib/neeto_deploy/cli/certificates/list.rb
194
238
  - lib/neeto_deploy/cli/dyno_console_manager.rb
195
239
  - lib/neeto_deploy/cli/env/commands.rb