jatai 1.3.0 → 1.5.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 +4 -4
- data/lib/jatai/cli.rb +1 -1
- data/lib/jatai/commands/base.rb +40 -1
- data/lib/jatai/commands/database.rb +1 -1
- data/lib/jatai/commands/deploy.rb +22 -2
- data/lib/jatai/commands/init.rb +41 -18
- data/lib/jatai/commands/redis.rb +3 -3
- data/lib/jatai/commands/scale.rb +1 -1
- data/lib/jatai/commands/status.rb +10 -4
- data/lib/jatai/commands/up.rb +40 -7
- data/lib/jatai/slug.rb +40 -0
- data/lib/jatai/version.rb +1 -1
- data/lib/jatai.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cce550fef38fbddcb460eaa4d1d5e2631dfc8b43e33d035c607680e24ea6fee6
|
|
4
|
+
data.tar.gz: 75eaffc25c616af78578cd43b077ef62604852b90a4319d961bc50b4190f067e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b16035861874a14cadbafff211bfb8517d30652187a0d428e377b5e3fff59a2f98aa0a04588bb8819e98aa1114429ea856e89432c7ef40657a987019eb63f4dc
|
|
7
|
+
data.tar.gz: 8a50d74a29189c5f21f62c1d16a73030fad4031be48a70957c6350c35d4935b14f6e76fd8b0fdb54365cc2f4a667519699e534e66913855293dfa6ac61fb1b29
|
data/lib/jatai/cli.rb
CHANGED
|
@@ -88,7 +88,7 @@ module Jatai
|
|
|
88
88
|
|
|
89
89
|
desc "scale", "Escalar a aplicação"
|
|
90
90
|
option :replicas, type: :numeric, desc: "Número de réplicas"
|
|
91
|
-
option :preset, type: :string, desc: "
|
|
91
|
+
option :preset, type: :string, desc: "Hex (base, escala_1, escala_2)"
|
|
92
92
|
def scale
|
|
93
93
|
Commands::Scale.new.execute(options)
|
|
94
94
|
end
|
data/lib/jatai/commands/base.rb
CHANGED
|
@@ -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!
|
|
@@ -31,6 +48,22 @@ module Jatai
|
|
|
31
48
|
abort pastel.red("Não autenticado. Execute `jatai login` primeiro.")
|
|
32
49
|
end
|
|
33
50
|
|
|
51
|
+
# Rótulo do cliente para a unidade computacional. O slug é interno
|
|
52
|
+
# (`base`, `escala_1`); quem lê a tela lê "Hex Escala 1" — o mesmo texto
|
|
53
|
+
# que o painel e a fatura mostram (AppsHelper#hex_label no servidor).
|
|
54
|
+
def hex_label(tier)
|
|
55
|
+
return "-" if tier.to_s.empty?
|
|
56
|
+
|
|
57
|
+
"Hex #{tier.to_s.tr("_", " ").capitalize}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# O banco é um addon; `database_plan` vem nulo no app que não tem nenhum.
|
|
61
|
+
def pg_label(plan)
|
|
62
|
+
return "sem banco" if plan.to_s.empty?
|
|
63
|
+
|
|
64
|
+
"PG #{plan.to_s.tr("_", " ").capitalize}"
|
|
65
|
+
end
|
|
66
|
+
|
|
34
67
|
def success(msg)
|
|
35
68
|
puts pastel.green(msg)
|
|
36
69
|
end
|
|
@@ -42,6 +75,12 @@ module Jatai
|
|
|
42
75
|
def info(msg)
|
|
43
76
|
puts pastel.cyan(msg)
|
|
44
77
|
end
|
|
78
|
+
|
|
79
|
+
# Nem verde nem vermelho: o deploy parcial subiu o site e deixou processo
|
|
80
|
+
# para trás — chamar de sucesso ou de falha mentiria nas duas direções.
|
|
81
|
+
def warning(msg)
|
|
82
|
+
puts pastel.yellow(msg)
|
|
83
|
+
end
|
|
45
84
|
end
|
|
46
85
|
end
|
|
47
86
|
end
|
|
@@ -14,7 +14,7 @@ module Jatai
|
|
|
14
14
|
db = helper.api.get("/api/v1/apps/#{slug}/database")
|
|
15
15
|
|
|
16
16
|
puts helper.pastel.bold("Banco de dados de #{slug}")
|
|
17
|
-
puts "
|
|
17
|
+
puts " Modo: #{db["mode"]}"
|
|
18
18
|
puts " Host: #{db["host"]}"
|
|
19
19
|
puts " Porta: #{db["port"]}"
|
|
20
20
|
puts " Nome: #{db["name"]}"
|
|
@@ -44,12 +44,15 @@ module Jatai
|
|
|
44
44
|
|
|
45
45
|
status = ws.wait_for("DeployStatusChannel",
|
|
46
46
|
params: { deploy_id: deploy_id },
|
|
47
|
-
until_status: %w[live failed],
|
|
47
|
+
until_status: %w[live failed partially_live],
|
|
48
48
|
timeout: DEPLOY_TIMEOUT)
|
|
49
49
|
|
|
50
50
|
elapsed = (Time.now - started).to_i
|
|
51
|
-
|
|
51
|
+
case status
|
|
52
|
+
when "live"
|
|
52
53
|
success("\nDeploy concluído com sucesso! (#{elapsed}s)")
|
|
54
|
+
when "partially_live"
|
|
55
|
+
print_partial_summary(slug, deploy_id, elapsed)
|
|
53
56
|
else
|
|
54
57
|
error("\nDeploy falhou.")
|
|
55
58
|
end
|
|
@@ -74,6 +77,9 @@ module Jatai
|
|
|
74
77
|
when "live"
|
|
75
78
|
success("\nDeploy concluído com sucesso! (#{(Time.now - started).to_i}s)")
|
|
76
79
|
return
|
|
80
|
+
when "partially_live"
|
|
81
|
+
print_partial_summary(slug, deploy_id, (Time.now - started).to_i)
|
|
82
|
+
return
|
|
77
83
|
when "failed"
|
|
78
84
|
error("\nDeploy falhou.")
|
|
79
85
|
return
|
|
@@ -92,6 +98,20 @@ module Jatai
|
|
|
92
98
|
error("\nTimeout (#{(Time.now - started).to_i}s). Verifique com: jatai status")
|
|
93
99
|
end
|
|
94
100
|
|
|
101
|
+
# O deploy parcial é terminal: o site subiu, mas processo(s) declarados
|
|
102
|
+
# ficaram no release anterior. Sem este ramo a CLI penduraria 20min
|
|
103
|
+
# esperando um "live" que não vem.
|
|
104
|
+
def print_partial_summary(slug, deploy_id, elapsed)
|
|
105
|
+
warning("\nDeploy parcialmente concluído (#{elapsed}s)")
|
|
106
|
+
deploy = api.get("/api/v1/apps/#{slug}/deploys/#{deploy_id}")
|
|
107
|
+
Array(deploy["process_failures"]).each do |failure|
|
|
108
|
+
warning(" #{failure['name']}: #{failure['reason']}")
|
|
109
|
+
end
|
|
110
|
+
warning("O site está no ar na versão nova; os processos acima seguem na anterior.")
|
|
111
|
+
rescue StandardError
|
|
112
|
+
warning(" (detalhe indisponível — ver o deploy no painel)")
|
|
113
|
+
end
|
|
114
|
+
|
|
95
115
|
def status_label(status)
|
|
96
116
|
{ "queued" => "Na fila...", "building" => "Construindo imagem...",
|
|
97
117
|
"deploying" => "Deployando..." }[status] || status
|
data/lib/jatai/commands/init.rb
CHANGED
|
@@ -5,24 +5,27 @@ require "yaml"
|
|
|
5
5
|
module Jatai
|
|
6
6
|
module Commands
|
|
7
7
|
class Init < Base
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
# O Hex é a unidade computacional da plataforma — web e worker custam o
|
|
9
|
+
# mesmo degrau. Os preços aqui são os que a fatura cobra (Pricing::HEX):
|
|
10
|
+
# esta lista já anunciou "hobby R$ 19" enquanto a plataforma debitava
|
|
11
|
+
# R$ 29, e o cliente só descobria na primeira fatura.
|
|
12
|
+
#
|
|
13
|
+
# O `Hex Dedicado` (R$ 299) fica de fora: ele reserva 2Gi, 51% do nó k3s
|
|
14
|
+
# atual, e só fica vendável após o retype da VM.
|
|
15
|
+
HEX = {
|
|
16
|
+
"base" => "Hex Base — R$ 29/mês (1 grátis por time)",
|
|
17
|
+
"escala_1" => "Hex Escala 1 — R$ 59/mês",
|
|
18
|
+
"escala_2" => "Hex Escala 2 — R$ 119/mês"
|
|
12
19
|
}.freeze
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
# Região única por enquanto (Sudeste). Quando houver multi-região, volta a ser um prompt.
|
|
22
|
+
DEFAULT_REGION = "br-se1".freeze
|
|
15
23
|
STACKS = {
|
|
16
24
|
"rails" => "Rails",
|
|
17
25
|
"node" => "Node.js",
|
|
18
26
|
"static" => "Estático (HTML/CSS/JS, sem build)",
|
|
19
27
|
"docker" => "Docker (custom Dockerfile)"
|
|
20
28
|
}.freeze
|
|
21
|
-
DATABASE_TIERS = {
|
|
22
|
-
"mini" => "Mini — 512MB, 20 conexões (Grátis)",
|
|
23
|
-
"essencial" => "Essencial — 1GB, 50 conexões (R$ 19/mês)",
|
|
24
|
-
"padrao" => "Padrão — 5GB, 100 conexões (R$ 49/mês)"
|
|
25
|
-
}.freeze
|
|
26
29
|
|
|
27
30
|
def execute(options = {})
|
|
28
31
|
prompt = TTY::Prompt.new
|
|
@@ -43,30 +46,50 @@ module Jatai
|
|
|
43
46
|
stack = prompt.select("Stack:", STACKS.map { |k, v| { name: v, value: k } })
|
|
44
47
|
end
|
|
45
48
|
|
|
46
|
-
name = prompt
|
|
47
|
-
preset = prompt.select("
|
|
48
|
-
region = prompt.select("Região:", REGIONS.map { |k, v| { name: "#{v} (#{k})", value: k } })
|
|
49
|
-
# Sites estáticos não têm banco — não pergunta o tier.
|
|
50
|
-
db_tier = stack == "static" ? nil : prompt.select("Banco de dados:", DATABASE_TIERS.map { |k, v| { name: v, value: k } })
|
|
49
|
+
name, slug = ask_name_and_slug(prompt)
|
|
50
|
+
preset = prompt.select("Hex:", HEX.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" =>
|
|
57
|
+
"app" => slug,
|
|
58
|
+
"name" => name,
|
|
55
59
|
"stack" => stack,
|
|
56
60
|
"preset" => preset,
|
|
57
|
-
"region" =>
|
|
61
|
+
"region" => DEFAULT_REGION,
|
|
58
62
|
"branch" => branch
|
|
59
63
|
}
|
|
60
|
-
config["database_tier"] = db_tier if db_tier
|
|
61
64
|
|
|
62
65
|
File.write(config_path, YAML.dump(config))
|
|
63
66
|
success("Arquivo .jatai.yml criado com sucesso!")
|
|
67
|
+
info("Identificador: #{slug}") if slug != name
|
|
64
68
|
info("Branch: #{branch}")
|
|
69
|
+
# O banco deixou de ser uma pergunta: o Provision::Orchestrator dá um
|
|
70
|
+
# Postgres a todo app não-estático, e o PG Base do app de cortesia do
|
|
71
|
+
# time é isento. Escolher tier aqui só antecipava uma decisão que a aba
|
|
72
|
+
# "Banco de dados" refaz melhor, com o app já rodando.
|
|
73
|
+
info("Banco: PG Base provisionado automaticamente no `jatai up`.") unless stack == "static"
|
|
65
74
|
info("Execute `jatai up` para criar e fazer deploy do app.")
|
|
66
75
|
end
|
|
67
76
|
|
|
68
77
|
private
|
|
69
78
|
|
|
79
|
+
# O nome é livre; o slug sai dele. Repete a pergunta quando nada
|
|
80
|
+
# aproveitável sobra (nome só de emoji, por exemplo) em vez de gravar um
|
|
81
|
+
# `app` vazio que quebraria a primeira requisição.
|
|
82
|
+
def ask_name_and_slug(prompt)
|
|
83
|
+
loop do
|
|
84
|
+
name = prompt.ask("Nome do app:") { |q| q.required true }
|
|
85
|
+
slug = Slug.from(name)
|
|
86
|
+
return [ name, slug ] if slug
|
|
87
|
+
|
|
88
|
+
error("Não consegui gerar um identificador a partir de #{name.inspect}. " \
|
|
89
|
+
"Use letras ou números no nome.")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
70
93
|
def detect_stack
|
|
71
94
|
if File.exist?(File.join(Dir.pwd, "Gemfile"))
|
|
72
95
|
gemfile = File.read(File.join(Dir.pwd, "Gemfile"))
|
data/lib/jatai/commands/redis.rb
CHANGED
data/lib/jatai/commands/scale.rb
CHANGED
|
@@ -18,7 +18,7 @@ module Jatai
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
estimate = api.post("/api/v1/apps/#{slug}/scale/estimate", body: body)
|
|
21
|
-
info("
|
|
21
|
+
info("Hex: #{hex_label(estimate["preset"])}")
|
|
22
22
|
info("Réplicas: #{estimate["replicas"]}") if estimate["replicas"]
|
|
23
23
|
info("CPU: #{estimate["cpu_limit"]} | RAM: #{estimate["memory_limit"]}") if estimate["cpu_limit"]
|
|
24
24
|
info("Custo estimado: R$ #{estimate["monthly_cost"]}/mês")
|
|
@@ -14,10 +14,16 @@ module Jatai
|
|
|
14
14
|
puts " Stack: #{app["stack"]}"
|
|
15
15
|
puts " Status: #{colorize_status(app["status"])}"
|
|
16
16
|
puts " Região: #{app["region"]}"
|
|
17
|
-
puts "
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
puts " Hex: #{hex_label(app["preset"])}"
|
|
18
|
+
# `database_tier` deixou de existir: o tier do banco vive no plano do
|
|
19
|
+
# addon, e é `database_plan` que a API devolve. Enquanto a CLI lia a
|
|
20
|
+
# chave antiga, esta linha saía sempre vazia.
|
|
21
|
+
puts " Banco: #{pg_label(app["database_plan"])}"
|
|
22
|
+
# A URL vem da API (ClientAppSerializer#url), como no `jatai up`: montá-la
|
|
23
|
+
# aqui hardcodando usebrasa.com.br fazia a CLI anunciar a marca antiga
|
|
24
|
+
# enquanto a documentação e o dashboard já mostram jataiapp.com.br — e
|
|
25
|
+
# deixava a CLI errada de novo no próximo passo do rebrand.
|
|
26
|
+
puts " URL: #{app["url"]}" if app["url"]
|
|
21
27
|
|
|
22
28
|
if app["last_deploy"]
|
|
23
29
|
deploy = app["last_deploy"]
|
data/lib/jatai/commands/up.rb
CHANGED
|
@@ -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/#{
|
|
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 #{
|
|
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,10 +92,9 @@ module Jatai
|
|
|
70
92
|
|
|
71
93
|
def create_app(config)
|
|
72
94
|
app_params = {
|
|
73
|
-
name:
|
|
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
|
-
app_params[:database_tier] = config[:database_tier] if config[:database_tier]
|
|
77
98
|
app_params[:release_command] = config.dig(:release, :command) if config.dig(:release, :command)
|
|
78
99
|
api.post("/api/v1/apps", body: { app: app_params })
|
|
79
100
|
end
|
|
@@ -145,11 +166,14 @@ module Jatai
|
|
|
145
166
|
|
|
146
167
|
status = ws.wait_for("DeployStatusChannel",
|
|
147
168
|
params: { deploy_id: deploy_id },
|
|
148
|
-
until_status: %w[live failed],
|
|
169
|
+
until_status: %w[live failed partially_live],
|
|
149
170
|
timeout: DEPLOY_TIMEOUT)
|
|
150
171
|
|
|
151
|
-
|
|
172
|
+
case status
|
|
173
|
+
when "live"
|
|
152
174
|
true
|
|
175
|
+
when "partially_live"
|
|
176
|
+
partial_deploy_notice
|
|
153
177
|
else
|
|
154
178
|
error("\nDeploy falhou.")
|
|
155
179
|
false
|
|
@@ -166,6 +190,14 @@ module Jatai
|
|
|
166
190
|
Jatai::Websocket::CableClient.new.tap(&:connect!)
|
|
167
191
|
end
|
|
168
192
|
|
|
193
|
+
# `true` porque, para o `up`, o app ESTÁ no ar: o fluxo seguinte (mostrar
|
|
194
|
+
# a URL) continua válido. O que ficou fora do release sai no painel e no
|
|
195
|
+
# `jatai deploy`, que tem o resumo completo.
|
|
196
|
+
def partial_deploy_notice
|
|
197
|
+
warning("\nDeploy parcialmente concluído — processo(s) fora do release; ver o deploy no painel.")
|
|
198
|
+
true
|
|
199
|
+
end
|
|
200
|
+
|
|
169
201
|
def warn_fallback(err)
|
|
170
202
|
$stderr.puts "\n [WebSocket indisponível: #{err.message}. Usando polling...]"
|
|
171
203
|
end
|
|
@@ -205,6 +237,7 @@ module Jatai
|
|
|
205
237
|
DEPLOY_MAX_POLLS.times do |i|
|
|
206
238
|
deploy = api.get("/api/v1/apps/#{slug}/deploys/#{deploy_id}")
|
|
207
239
|
return true if deploy["status"] == "live"
|
|
240
|
+
return partial_deploy_notice if deploy["status"] == "partially_live"
|
|
208
241
|
if deploy["status"] == "failed"
|
|
209
242
|
error("\nDeploy falhou.")
|
|
210
243
|
return false
|
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
data/lib/jatai.rb
CHANGED
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.
|
|
4
|
+
version: 1.5.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
|