jatai 1.3.0 → 1.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b9131e1a51d23a7120a320f01c24d31b332e636467c048bbfddea25bd49b643
4
- data.tar.gz: 5306e6ed76d3076ebe707d08f399d9071d63814fe92cd930239a2143113ba87a
3
+ metadata.gz: 648533180b943e80129355c3308d594f1867fde7946470361f3897ad3cdd06eb
4
+ data.tar.gz: 2cfe1d3481ef074b1c1e257f0cd2eea369193a6681694d5d2da6a8e6131f4c42
5
5
  SHA512:
6
- metadata.gz: ed82a595b2b854268d89902e495963b548b6b6a0057d9e846aa01345157fe5c02ea76935901aede74a668be8a8c6ed1c9a7f509702fd27e8f45b37cbf2f382c4
7
- data.tar.gz: 17fb609ef8532e4da8d49c8bd1cb8ada50fc3052772db9b4b625de6f6516fb830ab8de27264c9166c30eff5080b2adb0d07c827bda32c7184682a0488746e235
6
+ metadata.gz: 82d898f647e4109c8842d35d203fd818a170f013f174865ef91e5934a6afc93083e2dd4aa7dd305ef2922d7ab2897c0257f9dbd9fba8c763a021b4707851a7be
7
+ data.tar.gz: 444b6824a85488f361916f126e34bc68e574c7248bef66d35c51430cdaa9056ad0b18d6880a17ea856d2ef98ca1483f7df71ad991d08729c19630f6b053b99a8
@@ -22,8 +22,25 @@ module Jatai
22
22
  end
23
23
  end
24
24
 
25
+ # SEMPRE slugifica o que veio do arquivo. Isso é o que dá
26
+ # retrocompatibilidade com os .jatai.yml gerados antes de `app:` passar a
27
+ # guardar o slug: lá dentro havia o nome de exibição cru ("Alves
28
+ # Barbearia"), que ia direto para a URL e estourava no URI antes mesmo de
29
+ # sair a requisição. Slugificar é idempotente, então arquivo novo — que já
30
+ # traz o slug — atravessa sem alteração.
25
31
  def app_slug
26
- project_config[:app]
32
+ raw = project_config[:app]
33
+ slug = Slug.from(raw)
34
+ return slug if slug
35
+
36
+ abort pastel.red("`app` inválido no .jatai.yml (#{raw.inspect}). " \
37
+ "Use letras, números e hífen, ou rode `jatai init` novamente.")
38
+ end
39
+
40
+ # Nome legível. Arquivo novo traz `name:`; no antigo, o próprio `app:` era
41
+ # o nome de exibição.
42
+ def app_name
43
+ project_config[:name] || project_config[:app]
27
44
  end
28
45
 
29
46
  def require_auth!
@@ -11,7 +11,8 @@ module Jatai
11
11
  "pro" => "Pro — 2GB RAM, réplicas ilimitadas (R$ 69/mês)"
12
12
  }.freeze
13
13
 
14
- REGIONS = { "br-se1" => "São Paulo", "br-ne1" => "Nordeste" }.freeze
14
+ # Região única por enquanto (Sudeste). Quando houver multi-região, volta a ser um prompt.
15
+ DEFAULT_REGION = "br-se1".freeze
15
16
  STACKS = {
16
17
  "rails" => "Rails",
17
18
  "node" => "Node.js",
@@ -43,30 +44,48 @@ module Jatai
43
44
  stack = prompt.select("Stack:", STACKS.map { |k, v| { name: v, value: k } })
44
45
  end
45
46
 
46
- name = prompt.ask("Nome do app:") { |q| q.required true }
47
+ name, slug = ask_name_and_slug(prompt)
47
48
  preset = prompt.select("Preset:", PRESETS.map { |k, v| { name: v, value: k } })
48
- region = prompt.select("Região:", REGIONS.map { |k, v| { name: "#{v} (#{k})", value: k } })
49
49
  # Sites estáticos não têm banco — não pergunta o tier.
50
50
  db_tier = stack == "static" ? nil : prompt.select("Banco de dados:", DATABASE_TIERS.map { |k, v| { name: v, value: k } })
51
51
  branch = detect_git_branch || "main"
52
52
 
53
+ # `app` é o IDENTIFICADOR (slug) usado nas URLs da API; `name` é o nome de
54
+ # exibição. Antes um único campo servia aos dois papéis, e um nome com
55
+ # espaço ("Alves Barbearia") ia direto para a URL e estourava no URI.
53
56
  config = {
54
- "app" => name,
57
+ "app" => slug,
58
+ "name" => name,
55
59
  "stack" => stack,
56
60
  "preset" => preset,
57
- "region" => region,
61
+ "region" => DEFAULT_REGION,
58
62
  "branch" => branch
59
63
  }
60
64
  config["database_tier"] = db_tier if db_tier
61
65
 
62
66
  File.write(config_path, YAML.dump(config))
63
67
  success("Arquivo .jatai.yml criado com sucesso!")
68
+ info("Identificador: #{slug}") if slug != name
64
69
  info("Branch: #{branch}")
65
70
  info("Execute `jatai up` para criar e fazer deploy do app.")
66
71
  end
67
72
 
68
73
  private
69
74
 
75
+ # O nome é livre; o slug sai dele. Repete a pergunta quando nada
76
+ # aproveitável sobra (nome só de emoji, por exemplo) em vez de gravar um
77
+ # `app` vazio que quebraria a primeira requisição.
78
+ def ask_name_and_slug(prompt)
79
+ loop do
80
+ name = prompt.ask("Nome do app:") { |q| q.required true }
81
+ slug = Slug.from(name)
82
+ return [ name, slug ] if slug
83
+
84
+ error("Não consegui gerar um identificador a partir de #{name.inspect}. " \
85
+ "Use letras ou números no nome.")
86
+ end
87
+ end
88
+
70
89
  def detect_stack
71
90
  if File.exist?(File.join(Dir.pwd, "Gemfile"))
72
91
  gemfile = File.read(File.join(Dir.pwd, "Gemfile"))
@@ -46,14 +46,36 @@ module Jatai
46
46
 
47
47
  private
48
48
 
49
+ # O slug do .jatai.yml é só o PALPITE: o servidor pode ter desambiguado
50
+ # com sufixo (alves-barbearia-1) quando o nome já existia, e a partir daí
51
+ # o arquivo apontaria para sempre para um app que não é o do cliente.
52
+ # Depois de criar, grava de volta o slug que o servidor devolveu — daí em
53
+ # diante o arquivo carrega a verdade, não a dedução.
49
54
  def find_or_create_app(config)
50
- existing = api.get("/api/v1/apps/#{config[:app]}")
55
+ existing = api.get("/api/v1/apps/#{app_slug}")
51
56
  info("App #{existing["slug"]} encontrado (#{existing["status"]}).")
52
57
  ensure_repo_url(existing["slug"])
53
58
  existing
54
59
  rescue Api::Client::NotFoundError
55
- info("Criando app #{config[:app]}...")
56
- create_app(config)
60
+ info("Criando app #{app_name}...")
61
+ created = create_app(config)
62
+ persist_server_slug(created["slug"])
63
+ created
64
+ end
65
+
66
+ def persist_server_slug(server_slug)
67
+ return if server_slug.nil? || server_slug == project_config[:app]
68
+
69
+ path = Config.project_file_path
70
+ raw = YAML.safe_load(File.read(path))
71
+ raw["name"] ||= raw["app"] # arquivo antigo: `app` guardava o nome
72
+ raw["app"] = server_slug
73
+ File.write(path, YAML.dump(raw))
74
+ info("Identificador do app no .jatai.yml: #{server_slug}")
75
+ rescue StandardError => e
76
+ # Não derruba o deploy: o app foi criado e o deploy segue. Só o arquivo
77
+ # ficou desatualizado, e o próximo comando cai no palpite de novo.
78
+ warn(pastel.yellow(" Não foi possível gravar o slug no .jatai.yml: #{e.message}"))
57
79
  end
58
80
 
59
81
  def trigger_provision(slug)
@@ -70,7 +92,7 @@ module Jatai
70
92
 
71
93
  def create_app(config)
72
94
  app_params = {
73
- name: config[:app], stack: config[:stack], preset: config[:preset],
95
+ name: app_name, stack: config[:stack], preset: config[:preset],
74
96
  region: config[:region], repo_url: detect_git_remote, repo_branch: config[:branch]
75
97
  }
76
98
  app_params[:database_tier] = config[:database_tier] if config[:database_tier]
data/lib/jatai/slug.rb ADDED
@@ -0,0 +1,40 @@
1
+ module Jatai
2
+ # Converte texto livre no identificador que o servidor usa na URL.
3
+ #
4
+ # O servidor gera o slug com `name.parameterize` (ActiveSupport) e o valida
5
+ # com /\A[a-z0-9]([a-z0-9\-]*[a-z0-9])?\z/. A CLI não depende de
6
+ # ActiveSupport — carregá-la inteira por uma função encareceria o boot — então
7
+ # a transliteração é feita à mão para o conjunto que os clientes de fato usam
8
+ # (português). Caracteres fora dessa tabela são descartados, e é por isso que
9
+ # esta conversão é só o PALPITE da primeira requisição: quem manda no slug é o
10
+ # servidor, e o `jatai up` grava de volta no .jatai.yml o slug que ele retorna.
11
+ #
12
+ # Idempotente: aplicar sobre um slug válido devolve o mesmo slug. É o que dá
13
+ # retrocompatibilidade com os .jatai.yml antigos, em que `app:` guardava o
14
+ # nome de exibição — "Alves Barbearia" vira "alves-barbearia", que é
15
+ # exatamente o slug que o servidor gerou na criação daquele app.
16
+ module Slug
17
+ TRANSLITERATIONS = {
18
+ "á" => "a", "à" => "a", "â" => "a", "ã" => "a", "ä" => "a", "å" => "a",
19
+ "é" => "e", "è" => "e", "ê" => "e", "ë" => "e",
20
+ "í" => "i", "ì" => "i", "î" => "i", "ï" => "i",
21
+ "ó" => "o", "ò" => "o", "ô" => "o", "õ" => "o", "ö" => "o",
22
+ "ú" => "u", "ù" => "u", "û" => "u", "ü" => "u",
23
+ "ç" => "c", "ñ" => "n", "ý" => "y"
24
+ }.freeze
25
+
26
+ module_function
27
+
28
+ # nil quando não sobra nada utilizável — quem chama decide o que dizer ao
29
+ # usuário. Devolver "" mandaria uma URL vazia para a API.
30
+ def from(text)
31
+ return nil if text.nil?
32
+
33
+ slug = text.to_s.downcase
34
+ slug = slug.gsub(/[^a-z0-9\-]/) { |char| TRANSLITERATIONS.fetch(char, "-") }
35
+ slug = slug.gsub(/-+/, "-").gsub(/\A-|-\z/, "")
36
+
37
+ slug.empty? ? nil : slug
38
+ end
39
+ end
40
+ end
data/lib/jatai/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Jatai
2
- VERSION = "1.3.0"
2
+ VERSION = "1.4.0"
3
3
  end
data/lib/jatai.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "jatai/version"
2
2
  require "jatai/config"
3
+ require "jatai/slug"
3
4
  require "jatai/api/client"
4
5
  require "jatai/cli"
5
6
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jatai
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jataí Cloud
@@ -181,6 +181,7 @@ files:
181
181
  - lib/jatai/commands/status.rb
182
182
  - lib/jatai/commands/up.rb
183
183
  - lib/jatai/config.rb
184
+ - lib/jatai/slug.rb
184
185
  - lib/jatai/source_packer.rb
185
186
  - lib/jatai/version.rb
186
187
  - lib/jatai/websocket/cable_client.rb