jatai 1.0.0

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.
@@ -0,0 +1,63 @@
1
+ require "json"
2
+ require "fileutils"
3
+
4
+ module Jatai
5
+ class Config
6
+ CONFIG_DIR = File.expand_path("~/.jatai").freeze
7
+ CREDENTIALS_FILE = File.join(CONFIG_DIR, "credentials.json").freeze
8
+ PROJECT_FILE = ".jatai.yml".freeze
9
+
10
+ # Compat com a CLI anterior (brasa) — fallback leve (beta fechado).
11
+ LEGACY_CONFIG_DIR = File.expand_path("~/.brasa").freeze
12
+ LEGACY_CREDENTIALS_FILE = File.join(LEGACY_CONFIG_DIR, "credentials.json").freeze
13
+ LEGACY_PROJECT_FILE = ".brasa.yml".freeze
14
+
15
+ DEFAULT_API_URL = "https://api.usebrasa.com.br".freeze
16
+
17
+ class << self
18
+ def api_url
19
+ ENV.fetch("JATAI_API_URL") { ENV.fetch("BRASA_API_URL", DEFAULT_API_URL) }
20
+ end
21
+
22
+ def token
23
+ credentials["token"]
24
+ end
25
+
26
+ def save_token(token)
27
+ FileUtils.mkdir_p(CONFIG_DIR)
28
+ File.open(CREDENTIALS_FILE, "w", 0600) do |f|
29
+ f.write(JSON.generate({ token: token }))
30
+ end
31
+ end
32
+
33
+ def clear_token
34
+ File.delete(CREDENTIALS_FILE) if File.exist?(CREDENTIALS_FILE)
35
+ File.delete(LEGACY_CREDENTIALS_FILE) if File.exist?(LEGACY_CREDENTIALS_FILE)
36
+ end
37
+
38
+ def logged_in?
39
+ !token.nil? && !token.empty?
40
+ end
41
+
42
+ # Caminho do arquivo de projeto no diretório atual, com fallback ao legado.
43
+ def project_file_path
44
+ new_path = File.join(Dir.pwd, PROJECT_FILE)
45
+ return new_path if File.exist?(new_path)
46
+
47
+ legacy_path = File.join(Dir.pwd, LEGACY_PROJECT_FILE)
48
+ File.exist?(legacy_path) ? legacy_path : new_path
49
+ end
50
+
51
+ private
52
+
53
+ def credentials
54
+ file = File.exist?(CREDENTIALS_FILE) ? CREDENTIALS_FILE : LEGACY_CREDENTIALS_FILE
55
+ return {} unless File.exist?(file)
56
+
57
+ JSON.parse(File.read(file))
58
+ rescue JSON::ParserError
59
+ {}
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,52 @@
1
+ require "tmpdir"
2
+ require "open3"
3
+ require "securerandom"
4
+
5
+ module Jatai
6
+ class SourcePacker
7
+ IGNORE_PATTERNS = %w[
8
+ .git node_modules tmp log .bundle vendor/bundle
9
+ .env .env.* *.log coverage
10
+ config/master.key config/credentials.yml.enc
11
+ config/credentials config/credentials/*.yml.enc
12
+ *.pem *.key storage/*.key
13
+ ].freeze
14
+
15
+ def self.pack(directory = Dir.pwd)
16
+ new(directory).pack
17
+ end
18
+
19
+ def initialize(directory)
20
+ @directory = directory
21
+ end
22
+
23
+ def pack
24
+ archive_path = File.join(Dir.tmpdir, "jatai-source-#{Time.now.to_i}-#{SecureRandom.hex(4)}.tar.gz")
25
+
26
+ excludes = build_excludes
27
+ cmd = [ "tar", "czf", archive_path ] + excludes + [ "-C", @directory, "." ]
28
+
29
+ _out, err, status = Open3.capture3(*cmd)
30
+ raise "Falha ao empacotar código: #{err}" unless status.success?
31
+
32
+ archive_path
33
+ end
34
+
35
+ private
36
+
37
+ def build_excludes
38
+ patterns = IGNORE_PATTERNS.dup
39
+
40
+ dockerignore = File.join(@directory, ".dockerignore")
41
+ if File.exist?(dockerignore)
42
+ File.readlines(dockerignore).each do |line|
43
+ line = line.strip
44
+ next if line.empty? || line.start_with?("#")
45
+ patterns << line
46
+ end
47
+ end
48
+
49
+ patterns.flat_map { |p| [ "--exclude", p ] }
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module Jatai
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,146 @@
1
+ require "websocket-client-simple"
2
+ require "json"
3
+
4
+ module Jatai
5
+ module Websocket
6
+ class CableClient
7
+ class ConnectionError < StandardError; end
8
+
9
+ RECONNECT_DELAYS = [ 1, 3, 10 ].freeze
10
+
11
+ def initialize(api_url: nil, token: nil)
12
+ @api_url = api_url || Jatai::Config.api_url
13
+ @token = token || Jatai::Config.token
14
+ @subscriptions = {}
15
+ @connected = false
16
+ @log_callback = nil
17
+ end
18
+
19
+ def connect!
20
+ raise ConnectionError, "Token nao encontrado. Execute `jatai login`." unless @token
21
+
22
+ ws_url = @api_url.sub(%r{^https?://}, "wss://").chomp("/") + "/cable"
23
+ @ws = WebSocket::Client::Simple.connect(ws_url, headers: {
24
+ "Authorization" => "Bearer #{@token}",
25
+ "Origin" => @api_url
26
+ })
27
+
28
+ setup_handlers
29
+ wait_for_welcome
30
+ self
31
+ end
32
+
33
+ def subscribe(channel, params = {}, &callback)
34
+ identifier = { channel: channel }.merge(params).to_json
35
+ @subscriptions[identifier] = callback
36
+ send_command("subscribe", identifier)
37
+ identifier
38
+ end
39
+
40
+ def wait_for(channel, params: {}, until_status: [], timeout: 1800)
41
+ result = nil
42
+ identifier = subscribe(channel, params) do |msg|
43
+ status = msg["status"]
44
+ result = status if until_status.include?(status)
45
+ end
46
+
47
+ deadline = Time.now + timeout
48
+ loop do
49
+ return result if result
50
+ raise ConnectionError, "Timeout aguardando status (#{timeout}s)" if Time.now > deadline
51
+
52
+ unless @connected
53
+ reconnect!
54
+ # Re-subscribe after reconnect
55
+ @subscriptions.each_key { |id| send_command("subscribe", id) }
56
+ end
57
+
58
+ sleep 0.2
59
+ end
60
+ ensure
61
+ send_command("unsubscribe", identifier) if identifier && @connected
62
+ end
63
+
64
+ def on_log(&block)
65
+ @log_callback = block
66
+ end
67
+
68
+ def disconnect
69
+ @ws&.close
70
+ @connected = false
71
+ end
72
+
73
+ private
74
+
75
+ def setup_handlers
76
+ client = self
77
+
78
+ @ws.on :message do |msg|
79
+ begin
80
+ data = JSON.parse(msg.data)
81
+ client.send(:handle_message, data)
82
+ rescue JSON::ParserError
83
+ $stderr.puts "[WS] Mensagem malformada ignorada: #{msg.data.to_s[0..100]}" if ENV["JATAI_DEBUG"]
84
+ end
85
+ end
86
+
87
+ @ws.on :close do |_|
88
+ client.instance_variable_set(:@connected, false)
89
+ end
90
+
91
+ @ws.on :error do |_|
92
+ client.instance_variable_set(:@connected, false)
93
+ end
94
+ end
95
+
96
+ def handle_message(data)
97
+ case data["type"]
98
+ when "welcome"
99
+ @connected = true
100
+ when "ping"
101
+ # keepalive — noop
102
+ when "confirm_subscription"
103
+ # confirmed
104
+ when "reject_subscription"
105
+ @connected = false
106
+ else
107
+ identifier = data["identifier"]
108
+ message = data["message"]
109
+ return unless message
110
+
111
+ callback = @subscriptions[identifier]
112
+ callback&.call(message)
113
+
114
+ @log_callback&.call(message) if message["line"]
115
+ end
116
+ end
117
+
118
+ def wait_for_welcome
119
+ deadline = Time.now + 10
120
+ loop do
121
+ return if @connected
122
+ raise ConnectionError, "Timeout na conexao WebSocket" if Time.now > deadline
123
+ sleep 0.1
124
+ end
125
+ end
126
+
127
+ def reconnect!
128
+ RECONNECT_DELAYS.each do |delay|
129
+ sleep delay
130
+ begin
131
+ connect!
132
+ return
133
+ rescue StandardError
134
+ next
135
+ end
136
+ end
137
+ raise ConnectionError, "Falha na reconexao apos #{RECONNECT_DELAYS.size} tentativas"
138
+ end
139
+
140
+ def send_command(command, identifier)
141
+ return unless @ws&.open?
142
+ @ws.send({ command: command, identifier: identifier }.to_json)
143
+ end
144
+ end
145
+ end
146
+ end
data/lib/jatai.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "jatai/version"
2
+ require "jatai/config"
3
+ require "jatai/api/client"
4
+ require "jatai/cli"
5
+
6
+ module Jatai
7
+ end
metadata ADDED
@@ -0,0 +1,207 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jatai
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jataí Cloud
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: thor
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.3'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: faraday-multipart
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: tty-prompt
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.23'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.23'
68
+ - !ruby/object:Gem::Dependency
69
+ name: tty-spinner
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.9'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.9'
82
+ - !ruby/object:Gem::Dependency
83
+ name: tty-table
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.12'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.12'
96
+ - !ruby/object:Gem::Dependency
97
+ name: pastel
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.8'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0.8'
110
+ - !ruby/object:Gem::Dependency
111
+ name: websocket-client-simple
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.8'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '0.8'
124
+ - !ruby/object:Gem::Dependency
125
+ name: rspec
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '3.13'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '3.13'
138
+ - !ruby/object:Gem::Dependency
139
+ name: webmock
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '3.24'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '3.24'
152
+ description: Deploy apps Rails e Node.js na nuvem brasileira com um comando. Soberania
153
+ de dados, faturamento em reais.
154
+ email:
155
+ - contato@jataicloud.com.br
156
+ executables:
157
+ - jatai
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - LICENSE.txt
162
+ - README.md
163
+ - exe/jatai
164
+ - lib/jatai.rb
165
+ - lib/jatai/api/client.rb
166
+ - lib/jatai/cli.rb
167
+ - lib/jatai/commands/base.rb
168
+ - lib/jatai/commands/database.rb
169
+ - lib/jatai/commands/deploy.rb
170
+ - lib/jatai/commands/destroy.rb
171
+ - lib/jatai/commands/domains.rb
172
+ - lib/jatai/commands/env.rb
173
+ - lib/jatai/commands/init.rb
174
+ - lib/jatai/commands/login.rb
175
+ - lib/jatai/commands/logs.rb
176
+ - lib/jatai/commands/logs_export.rb
177
+ - lib/jatai/commands/logs_search.rb
178
+ - lib/jatai/commands/redis.rb
179
+ - lib/jatai/commands/scale.rb
180
+ - lib/jatai/commands/status.rb
181
+ - lib/jatai/commands/up.rb
182
+ - lib/jatai/config.rb
183
+ - lib/jatai/source_packer.rb
184
+ - lib/jatai/version.rb
185
+ - lib/jatai/websocket/cable_client.rb
186
+ homepage: https://jataicloud.com.br
187
+ licenses:
188
+ - MIT
189
+ metadata: {}
190
+ rdoc_options: []
191
+ require_paths:
192
+ - lib
193
+ required_ruby_version: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ version: '3.1'
198
+ required_rubygems_version: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ requirements: []
204
+ rubygems_version: 3.6.9
205
+ specification_version: 4
206
+ summary: CLI para deploy na Magalu Cloud
207
+ test_files: []