neetodeploy 1.1.24 → 1.1.26

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: 47ad21898f81d64148aa108316a77f64b143fe49f3d3c922a1e77ce6c2c22293
4
+ data.tar.gz: 9edf8e55e2594f7e0ff41df9919b38d9df2d8023316e5d3156b889efbe4e6e60
5
5
  SHA512:
6
- metadata.gz: b724ca4ea923f1be277dc7feb878968699b883f5548410a9668a57fbcbf7db410a215dc3112fb6697c248f24f4dcec0d6810ebdbc6d258c2e78cadc5399c203b
7
- data.tar.gz: c1e39a69f4085f5f31950b6530d5f4faed4351cf49b945024a1a2406ec16d666d9102e7bc039b4319eacd5d0ec5ab76782be511af7c6bc08d304a61ff34055ba
6
+ metadata.gz: 1be748f7bae2c04057b42f971caa1b2d7a4b2ae945263d5709a183faf5cc51089be6ab78e9d7bb97682651ef81453d5d28d92f9b58381e680ec706c41974f977
7
+ data.tar.gz: 84a68d7b3cc97a14698a7433d227a51e7bffbb7a3f2fb5a08a6e056d3d86e1ec9d653a9d6deb65f70dd1adc7a11b8f3348813b5286a3575a152f308c0a5431eb
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,109 @@ 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
+ print_github_repo_name
32
+ response = fetch_logs
33
+ @spinner.stop
34
+
35
+ unless response.success?
36
+ ui.error(response["error"] || response.message)
37
+ return
38
+ end
39
+
40
+ data = JSON.parse(response.body)
41
+
42
+ unless data["configured"]
43
+ ui.error("Logs are not configured for this app.")
44
+ return
45
+ end
31
46
 
32
- log_params = JSON[response.body]
33
- @log_client = cloud_watch_client(log_params)
34
- start_log_streaming(log_params)
47
+ # Initial batch returned DESC — reverse for chronological display
48
+ logs = data["logs"].reverse
49
+ logs.each { |log| print_log(log) }
50
+ # Subtract 10s buffer to account for potential client/server clock skew
51
+ @last_timestamp_ms = logs.any? ? logs.last["timestamp"] : ((Time.now.to_f - 10) * 1000).to_i
52
+
53
+ poll_for_logs
35
54
  end
36
55
 
37
56
  private
38
57
 
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
58
+ def fetch_logs(after_timestamp_ms: nil)
59
+ params = { app_slug:, process_type:, limit: INITIAL_LIMIT }
60
+
61
+ if after_timestamp_ms
62
+ params[:after_timestamp] = ms_to_iso8601(after_timestamp_ms)
63
+ params[:limit] = POLL_LIMIT
64
+ end
44
65
 
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
- )
66
+ send_get_request(logs_v2_url, params)
56
67
  end
57
68
 
58
- def start_spinner
59
- @spinner = TTY::Spinner.new("Starting live stream session [:spinner]", format: :classic)
60
- @spinner.auto_spin
69
+ def print_github_repo_name
70
+ response = send_get_request(app_details_url(app_slug), {})
71
+ return unless response.success?
72
+
73
+ data = JSON.parse(response.body)
74
+ repo_name = data.dig("app", "github_repo_name").to_s
75
+ ui.info("GitHub repository: #{repo_name}") if repo_name.strip != ""
76
+ rescue StandardError
77
+ nil
61
78
  end
62
79
 
63
- def start_log_streaming(log_params)
64
- next_token = nil
65
- @spinner.stop
80
+ def poll_for_logs
66
81
  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
82
+ response = fetch_logs(after_timestamp_ms: @last_timestamp_ms)
83
+
84
+ if response.success?
85
+ data = JSON.parse(response.body)
86
+ logs = data["logs"]
87
+ logs.each { |log| print_log(log) }
88
+ @last_timestamp_ms = logs.last["timestamp"] if logs.any?
89
+ next if data["has_more_forward"]
90
+ elsif response.code.between?(400, 499)
91
+ ui.error("Error #{response.code}: #{response["error"] || response.message}")
92
+ break
93
+ else
92
94
  warn "Connection lost. Reconnecting..."
93
- sleep 5
94
95
  end
95
- sleep 2
96
+
97
+ sleep POLL_INTERVAL_SECONDS
98
+ rescue Interrupt
99
+ raise
100
+ rescue => e
101
+ warn "Error: #{e.message}. Reconnecting..."
102
+ sleep 5
96
103
  end
97
104
  end
105
+
106
+ def print_log(log)
107
+ timestamp = Time.at(log["timestamp"] / 1000.0)
108
+ puts "#{timestamp.to_s.colorize(:magenta)} #{log["message"]}"
109
+ end
110
+
111
+ def ms_to_iso8601(ms)
112
+ Time.at(ms / 1000.0).utc.iso8601(3)
113
+ end
114
+
115
+ def start_spinner
116
+ @spinner = TTY::Spinner.new("Starting live stream session [:spinner]", format: :classic)
117
+ @spinner.auto_spin
118
+ end
98
119
  end
99
120
  end
100
121
  end
@@ -4,10 +4,16 @@ 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_APPS_URL = "#{NEETO_DEPLOY_CLI_V2_API_BASE_URL}/apps".freeze
9
+ NEETO_DEPLOY_CLI_API_V2_LOGS_URL = "#{NEETO_DEPLOY_CLI_V2_API_BASE_URL}/logs".freeze
8
10
 
9
- def authenticate_live_stream_url
10
- NEETO_DEPLOY_CLI_API_ENVIRONMENT_VARIABLES_URL
11
+ def logs_v2_url
12
+ NEETO_DEPLOY_CLI_API_V2_LOGS_URL
13
+ end
14
+
15
+ def app_details_url(app_slug)
16
+ "#{NEETO_DEPLOY_CLI_API_V2_APPS_URL}/#{app_slug}"
11
17
  end
12
18
  end
13
19
  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
@@ -19,7 +19,7 @@ module NeetoDeploy
19
19
  end
20
20
 
21
21
  def send_get_request(url, body)
22
- HTTParty.get(url, { body: common_body.merge(body), format: :json })
22
+ HTTParty.get(url, { query: common_body.merge(body), format: :json })
23
23
  end
24
24
 
25
25
  def send_post_request(url, body)
@@ -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.26"
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.26
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
@@ -239,7 +283,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
239
283
  - !ruby/object:Gem::Version
240
284
  version: '0'
241
285
  requirements: []
242
- rubygems_version: 3.4.19
286
+ rubygems_version: 3.5.10
243
287
  signing_key:
244
288
  specification_version: 4
245
289
  summary: CLI for neetoDeploy