@hadcloud/deploy-cli 0.1.1 → 0.1.3
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.
- package/README.md +202 -0
- package/dist/index.js +266 -2
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# HAD Deploy CLI
|
|
2
|
+
|
|
3
|
+
> Crie, publique e acompanhe aplicações no HAD Deploy diretamente pelo terminal — sem expor o provider de infraestrutura.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@hadcloud/deploy-cli)
|
|
6
|
+
[](https://nodejs.org/)
|
|
7
|
+
|
|
8
|
+
## O que você pode fazer
|
|
9
|
+
|
|
10
|
+
- Gerenciar projetos: listar, criar, editar e excluir.
|
|
11
|
+
- Criar, editar e excluir aplicações Git, Docker, Dockerfile e templates aprovados.
|
|
12
|
+
- Listar templates públicos e serviços provisionados.
|
|
13
|
+
- Publicar aplicações, iniciar/parar/reiniciar serviços e acompanhar execuções.
|
|
14
|
+
- Consultar status e logs de deploys de aplicações e de templates.
|
|
15
|
+
- Usar tokens com escopo mínimo, inclusive por projeto, em estações locais ou CI/CD.
|
|
16
|
+
|
|
17
|
+
## Instalação
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @hadcloud/deploy-cli
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Confira se a instalação está disponível:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
had config
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Começo rápido
|
|
30
|
+
|
|
31
|
+
1. No painel HAD Deploy, abra **CLI & MCP** e gere um token do tipo **CLI**.
|
|
32
|
+
2. Para acesso completo da CLI, selecione os escopos recomendados:
|
|
33
|
+
|
|
34
|
+
- `projects:read`, `projects:write`
|
|
35
|
+
- `apps:read`, `apps:write`
|
|
36
|
+
- `deployments:read`, `deployments:create`
|
|
37
|
+
- `logs:read`
|
|
38
|
+
- `env:write` somente se precisar editar variáveis de templates
|
|
39
|
+
|
|
40
|
+
3. Faça login:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
had login --api-url https://api.seu-dominio.com
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
O token é solicitado sem aparecer no terminal e fica armazenado no cofre do sistema operacional. No Windows, a CLI usa Windows DPAPI.
|
|
47
|
+
|
|
48
|
+
4. Confira os projetos disponíveis:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
had projects list
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Atualizar token
|
|
55
|
+
|
|
56
|
+
Não é preciso executar logout para trocar um token expirado ou criar um token com novos escopos:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
had token update
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A URL de API já configurada é mantida. Você também pode executar `had login` novamente para substituir o token.
|
|
63
|
+
|
|
64
|
+
Para automação sem prompt:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
echo "$HAD_DEPLOY_TOKEN" | had token update --token-stdin
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
> No PowerShell, use: `$env:NOVO_TOKEN | had token update --token-stdin`.
|
|
71
|
+
|
|
72
|
+
## Projetos
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Listar
|
|
76
|
+
had projects list
|
|
77
|
+
|
|
78
|
+
# Criar
|
|
79
|
+
had projects create "Plataforma de produção" --description "Serviços do cliente"
|
|
80
|
+
|
|
81
|
+
# Editar
|
|
82
|
+
had projects edit prj_... --name "Produção"
|
|
83
|
+
|
|
84
|
+
# Excluir — requer confirmação explícita
|
|
85
|
+
had projects delete prj_... --yes
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Aplicações
|
|
89
|
+
|
|
90
|
+
### Aplicação de um template aprovado
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Ver templates disponíveis
|
|
94
|
+
had templates list
|
|
95
|
+
|
|
96
|
+
# Provisionar n8n em um projeto
|
|
97
|
+
had apps create prj_... --template n8n --region BR-SP-01
|
|
98
|
+
|
|
99
|
+
# Forma equivalente
|
|
100
|
+
had templates create prj_... n8n --region BR-SP-01
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
O comando mostra o ID do serviço (`tpl_...`) e, quando o provider já o registrou, o ID da execução. Para acompanhar um deploy de template:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
had templates services prj_...
|
|
107
|
+
had templates deployments prj_... tpl_...
|
|
108
|
+
had templates status prj_... tpl_... <deployment-id>
|
|
109
|
+
had templates logs prj_... tpl_... <deployment-id> 300
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Aplicação de repositório GitHub
|
|
113
|
+
|
|
114
|
+
Primeiro conecte sua conta GitHub no painel HAD Deploy. A CLI recebe apenas o identificador da conexão segura já cadastrada — nunca token, senha ou URL Git com credenciais.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
had apps create prj_... \
|
|
118
|
+
--name minha-api \
|
|
119
|
+
--source github \
|
|
120
|
+
--git-connection git_... \
|
|
121
|
+
--repository https://github.com/minha-org/minha-api.git \
|
|
122
|
+
--branch main \
|
|
123
|
+
--build-type railpack
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Fontes aceitas: `github`, `git`, `docker` e `dockerfile`.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Listar aplicações de um projeto
|
|
130
|
+
had apps list prj_...
|
|
131
|
+
|
|
132
|
+
# Alterar configurações suportadas
|
|
133
|
+
had apps edit prj_... app_... --branch release --build-type dockerfile
|
|
134
|
+
|
|
135
|
+
# Solicitar deploy de uma aplicação
|
|
136
|
+
had deploy prj_... app_...
|
|
137
|
+
|
|
138
|
+
# Consultar o deploy interno da aplicação
|
|
139
|
+
had status dep_...
|
|
140
|
+
had logs dep_... 200
|
|
141
|
+
|
|
142
|
+
# Excluir a aplicação
|
|
143
|
+
had apps delete prj_... app_... --yes
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Gerenciar um serviço de template
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Reexecutar um deploy
|
|
150
|
+
had templates deploy prj_... tpl_...
|
|
151
|
+
|
|
152
|
+
# Atualizar variáveis pelo arquivo local
|
|
153
|
+
had templates edit prj_... tpl_... --env-file .env.production
|
|
154
|
+
|
|
155
|
+
# Excluir o serviço
|
|
156
|
+
had templates delete prj_... tpl_... --yes
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Os IDs de deploy de template pertencem ao provider e não são `dep_...`. Por isso, use `had templates status` e `had templates logs` para eles; `had status` e `had logs` são destinados a aplicações que utilizam a fila interna do HAD Deploy.
|
|
160
|
+
|
|
161
|
+
## Uso em CI/CD
|
|
162
|
+
|
|
163
|
+
Prefira variáveis protegidas do provedor de CI. Não grave tokens em `.env` versionado, código-fonte ou logs.
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
export HAD_DEPLOY_API_URL="https://api.seu-dominio.com"
|
|
167
|
+
export HAD_DEPLOY_TOKEN="hd_cli_..."
|
|
168
|
+
|
|
169
|
+
had projects list
|
|
170
|
+
had deploy prj_... app_...
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Quando `HAD_DEPLOY_TOKEN` e `HAD_DEPLOY_API_URL` estão definidos, a CLI não precisa de login interativo nem persiste credenciais locais.
|
|
174
|
+
|
|
175
|
+
## Segurança
|
|
176
|
+
|
|
177
|
+
- Gere tokens com o menor conjunto de escopos possível.
|
|
178
|
+
- Para um único projeto, crie um token limitado ao projeto no painel.
|
|
179
|
+
- Tokens limitados a projeto não podem criar novos projetos nem acessar recursos de outro projeto.
|
|
180
|
+
- Comandos destrutivos exigem `--yes`.
|
|
181
|
+
- O manifesto Compose de templates permanece controlado pela plataforma; a CLI não aceita Compose arbitrário.
|
|
182
|
+
- Revogue tokens não utilizados pelo painel HAD Deploy.
|
|
183
|
+
|
|
184
|
+
## Referência rápida
|
|
185
|
+
|
|
186
|
+
```text
|
|
187
|
+
had login [--api-url <url>] [--token-stdin]
|
|
188
|
+
had token update [--token-stdin]
|
|
189
|
+
had logout
|
|
190
|
+
had config
|
|
191
|
+
|
|
192
|
+
had projects list|create|edit|delete
|
|
193
|
+
had apps list|create|edit|delete
|
|
194
|
+
had templates list|create|services|deploy|deployments|status|logs|edit|delete
|
|
195
|
+
had deploy <project-id> <app-id>
|
|
196
|
+
had status <deployment-id>
|
|
197
|
+
had logs <deployment-id> [tail]
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Suporte
|
|
201
|
+
|
|
202
|
+
Abra o painel HAD Deploy para criar conexões Git, emitir ou revogar tokens e acompanhar a infraestrutura. Ao solicitar suporte, envie o ID público do recurso (`prj_`, `app_`, `tpl_` ou `dep_`) e o horário aproximado da operação — nunca envie o token.
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
3
|
import { stdin as input, stdout as output } from "node:process";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
4
5
|
import { credentialStatus, isToken, loadCredentials, normalizeApiUrl, removeCredentials, saveCredentials, } from "./credentials.js";
|
|
5
6
|
function usage() {
|
|
6
7
|
console.error(`HAD Deploy CLI
|
|
7
8
|
|
|
8
9
|
Uso:
|
|
9
10
|
had login [--api-url https://api.exemplo.com] [--token-stdin]
|
|
11
|
+
had token update [--token-stdin]
|
|
10
12
|
had logout
|
|
11
13
|
had config
|
|
12
14
|
had projects list
|
|
15
|
+
had projects create <name> [--description <text>]
|
|
16
|
+
had projects edit <project-id> [--name <name>] [--description <text>]
|
|
17
|
+
had projects delete <project-id> --yes
|
|
13
18
|
had apps list <project-id>
|
|
14
|
-
had templates list
|
|
15
19
|
had apps create <project-id> --template <template-id> [--region <region-code>]
|
|
20
|
+
had apps create <project-id> --name <name> --source github --git-connection <id> --repository <url> [--branch <branch>] [--build-type <type>]
|
|
21
|
+
had apps edit <project-id> <app-id> [--name <name>] [--branch <branch>] [--build-type <type>]
|
|
22
|
+
had apps delete <project-id> <app-id> --yes
|
|
23
|
+
had templates list
|
|
24
|
+
had templates create <project-id> <template-id> [--region <region-code>]
|
|
25
|
+
had templates services <project-id>
|
|
26
|
+
had templates deploy <project-id> <service-id>
|
|
27
|
+
had templates deployments <project-id> <service-id>
|
|
28
|
+
had templates status <project-id> <service-id> <deployment-id>
|
|
29
|
+
had templates logs <project-id> <service-id> <deployment-id> [tail]
|
|
30
|
+
had templates edit <project-id> <service-id> --env-file <path>
|
|
31
|
+
had templates delete <project-id> <service-id> --yes
|
|
16
32
|
had deploy <project-id> <app-id>
|
|
17
33
|
had status <deployment-id>
|
|
18
34
|
had logs <deployment-id> [tail]
|
|
@@ -36,6 +52,10 @@ async function request(path, init) {
|
|
|
36
52
|
const payload = (await response.json().catch(() => ({})));
|
|
37
53
|
throw new Error(payload.error?.message ?? `A API retornou HTTP ${response.status}.`);
|
|
38
54
|
}
|
|
55
|
+
if (response.status === 204 ||
|
|
56
|
+
response.headers.get("content-length") === "0") {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
39
59
|
return response.json();
|
|
40
60
|
}
|
|
41
61
|
async function prompt(question) {
|
|
@@ -89,9 +109,10 @@ async function promptSecret(question) {
|
|
|
89
109
|
}
|
|
90
110
|
async function login(args) {
|
|
91
111
|
const apiUrlIndex = args.indexOf("--api-url");
|
|
112
|
+
const configuredApiUrl = (await credentialStatus()).apiUrl;
|
|
92
113
|
const apiInput = apiUrlIndex >= 0
|
|
93
114
|
? args[apiUrlIndex + 1]
|
|
94
|
-
: await prompt("URL da API HAD Deploy: ");
|
|
115
|
+
: (configuredApiUrl ?? (await prompt("URL da API HAD Deploy: ")));
|
|
95
116
|
if (!apiInput)
|
|
96
117
|
throw new Error("A URL da API é obrigatória.");
|
|
97
118
|
const fromStdin = args.includes("--token-stdin");
|
|
@@ -133,10 +154,34 @@ function optionValue(args, option) {
|
|
|
133
154
|
throw new Error(`Informe um valor para ${option}.`);
|
|
134
155
|
return value;
|
|
135
156
|
}
|
|
157
|
+
function requireOption(args, option) {
|
|
158
|
+
const value = optionValue(args, option);
|
|
159
|
+
if (!value)
|
|
160
|
+
throw new Error(`Informe ${option}.`);
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
function requireConfirmation(args) {
|
|
164
|
+
if (!args.includes("--yes")) {
|
|
165
|
+
throw new Error("Esta operação é destrutiva. Confirme explicitamente com --yes.");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function integerOption(args, option) {
|
|
169
|
+
const value = optionValue(args, option);
|
|
170
|
+
if (!value)
|
|
171
|
+
return undefined;
|
|
172
|
+
const number = Number(value);
|
|
173
|
+
if (!Number.isInteger(number) || number < 1 || number > 65_535) {
|
|
174
|
+
throw new Error(`${option} deve ser um número inteiro válido.`);
|
|
175
|
+
}
|
|
176
|
+
return number;
|
|
177
|
+
}
|
|
136
178
|
export async function main(args) {
|
|
137
179
|
const [group, action, resourceId, extra] = args;
|
|
138
180
|
if (group === "login")
|
|
139
181
|
return login(args.slice(1));
|
|
182
|
+
if (group === "token" && action === "update" && !resourceId) {
|
|
183
|
+
return login(args.slice(2));
|
|
184
|
+
}
|
|
140
185
|
if (group === "logout" && !action) {
|
|
141
186
|
await removeCredentials();
|
|
142
187
|
console.log("Credenciais locais removidas.");
|
|
@@ -156,6 +201,47 @@ export async function main(args) {
|
|
|
156
201
|
})));
|
|
157
202
|
return;
|
|
158
203
|
}
|
|
204
|
+
if (group === "projects" && action === "create" && resourceId) {
|
|
205
|
+
const description = optionValue(args, "--description");
|
|
206
|
+
const response = await request("/api/v1/projects", {
|
|
207
|
+
method: "POST",
|
|
208
|
+
headers: {
|
|
209
|
+
"content-type": "application/json",
|
|
210
|
+
"idempotency-key": crypto.randomUUID(),
|
|
211
|
+
},
|
|
212
|
+
body: JSON.stringify({
|
|
213
|
+
name: resourceId,
|
|
214
|
+
...(description ? { description } : {}),
|
|
215
|
+
}),
|
|
216
|
+
});
|
|
217
|
+
console.log(`Projeto ${response.project.name} (${response.project.id}) criado: ${response.project.status}`);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (group === "projects" && action === "edit" && resourceId) {
|
|
221
|
+
const name = optionValue(args, "--name");
|
|
222
|
+
const description = optionValue(args, "--description");
|
|
223
|
+
if (!name && !description) {
|
|
224
|
+
throw new Error("Informe pelo menos --name ou --description.");
|
|
225
|
+
}
|
|
226
|
+
const response = await request(`/api/v1/projects/${encodeURIComponent(resourceId)}`, {
|
|
227
|
+
method: "PATCH",
|
|
228
|
+
headers: { "content-type": "application/json" },
|
|
229
|
+
body: JSON.stringify({
|
|
230
|
+
...(name ? { name } : {}),
|
|
231
|
+
...(description ? { description } : {}),
|
|
232
|
+
}),
|
|
233
|
+
});
|
|
234
|
+
console.log(`Projeto ${response.project.name} (${response.project.id}) atualizado: ${response.project.status}`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (group === "projects" && action === "delete" && resourceId) {
|
|
238
|
+
requireConfirmation(args);
|
|
239
|
+
await request(`/api/v1/projects/${encodeURIComponent(resourceId)}`, {
|
|
240
|
+
method: "DELETE",
|
|
241
|
+
});
|
|
242
|
+
console.log(`Projeto ${resourceId} excluído.`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
159
245
|
if (group === "apps" && action === "list" && resourceId) {
|
|
160
246
|
const { applications } = await request(`/api/v1/automation/projects/${encodeURIComponent(resourceId)}/apps`);
|
|
161
247
|
printRows(applications.map((application) => ({
|
|
@@ -176,9 +262,146 @@ export async function main(args) {
|
|
|
176
262
|
})));
|
|
177
263
|
return;
|
|
178
264
|
}
|
|
265
|
+
if (group === "templates" && action === "create" && resourceId && extra) {
|
|
266
|
+
return main([
|
|
267
|
+
"apps",
|
|
268
|
+
"create",
|
|
269
|
+
resourceId,
|
|
270
|
+
"--template",
|
|
271
|
+
extra,
|
|
272
|
+
...args.slice(4),
|
|
273
|
+
]);
|
|
274
|
+
}
|
|
275
|
+
if (group === "templates" && action === "services" && resourceId) {
|
|
276
|
+
const { services } = await request(`/api/v1/automation/projects/${encodeURIComponent(resourceId)}/templates`);
|
|
277
|
+
printRows(services.map((service) => ({
|
|
278
|
+
id: service.id,
|
|
279
|
+
name: service.name,
|
|
280
|
+
template: service.templateId,
|
|
281
|
+
version: service.templateVersion ?? "latest",
|
|
282
|
+
status: service.status,
|
|
283
|
+
})));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (group === "templates" && action === "deploy" && resourceId && extra) {
|
|
287
|
+
const response = await request(`/api/v1/projects/${encodeURIComponent(resourceId)}/templates/${encodeURIComponent(extra)}/deploy`, { method: "POST" });
|
|
288
|
+
if (response.deployment) {
|
|
289
|
+
console.log(`Deploy ${response.deployment.id} solicitado: ${response.deployment.status}`);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
console.log("Deploy solicitado. A execução ainda está sendo registrada pelo provider.");
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (group === "templates" &&
|
|
297
|
+
action === "deployments" &&
|
|
298
|
+
resourceId &&
|
|
299
|
+
extra) {
|
|
300
|
+
const { deployments } = await request(`/api/v1/projects/${encodeURIComponent(resourceId)}/templates/${encodeURIComponent(extra)}/deployments`);
|
|
301
|
+
printRows(deployments.map((deployment) => ({
|
|
302
|
+
id: deployment.id,
|
|
303
|
+
status: deployment.status,
|
|
304
|
+
trigger: deployment.trigger,
|
|
305
|
+
created: deployment.createdAt,
|
|
306
|
+
title: deployment.title ?? "-",
|
|
307
|
+
})));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (group === "templates" &&
|
|
311
|
+
action === "status" &&
|
|
312
|
+
resourceId &&
|
|
313
|
+
extra &&
|
|
314
|
+
args[4]) {
|
|
315
|
+
const deploymentId = args[4];
|
|
316
|
+
const { deployments } = await request(`/api/v1/projects/${encodeURIComponent(resourceId)}/templates/${encodeURIComponent(extra)}/deployments`);
|
|
317
|
+
const deployment = deployments.find((candidate) => candidate.id === deploymentId);
|
|
318
|
+
if (!deployment) {
|
|
319
|
+
throw new Error("Deployment de template não encontrado para este serviço.");
|
|
320
|
+
}
|
|
321
|
+
printRows([
|
|
322
|
+
{
|
|
323
|
+
id: deployment.id,
|
|
324
|
+
status: deployment.status,
|
|
325
|
+
trigger: deployment.trigger,
|
|
326
|
+
created: deployment.createdAt,
|
|
327
|
+
title: deployment.title ?? "-",
|
|
328
|
+
},
|
|
329
|
+
]);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (group === "templates" &&
|
|
333
|
+
action === "logs" &&
|
|
334
|
+
resourceId &&
|
|
335
|
+
extra &&
|
|
336
|
+
args[4]) {
|
|
337
|
+
const deploymentId = args[4];
|
|
338
|
+
const tail = args[5] ?? "200";
|
|
339
|
+
if (!/^\d+$/.test(tail))
|
|
340
|
+
throw new Error("tail deve ser numérico.");
|
|
341
|
+
const { logs } = await request(`/api/v1/projects/${encodeURIComponent(resourceId)}/templates/${encodeURIComponent(extra)}/deployments/${encodeURIComponent(deploymentId)}/logs?tail=${encodeURIComponent(tail)}`);
|
|
342
|
+
for (const log of logs)
|
|
343
|
+
console.log(`[${log.stream}] ${log.message}`);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (group === "templates" && action === "edit" && resourceId && extra) {
|
|
347
|
+
const envFile = requireOption(args, "--env-file");
|
|
348
|
+
const env = await readFile(envFile, "utf8");
|
|
349
|
+
await request(`/api/v1/projects/${encodeURIComponent(resourceId)}/templates/${encodeURIComponent(extra)}/env`, {
|
|
350
|
+
method: "PUT",
|
|
351
|
+
headers: { "content-type": "application/json" },
|
|
352
|
+
body: JSON.stringify({ env }),
|
|
353
|
+
});
|
|
354
|
+
console.log(`Variáveis do serviço ${extra} atualizadas.`);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (group === "templates" && action === "delete" && resourceId && extra) {
|
|
358
|
+
requireConfirmation(args);
|
|
359
|
+
await request(`/api/v1/projects/${encodeURIComponent(resourceId)}/templates/${encodeURIComponent(extra)}`, { method: "DELETE" });
|
|
360
|
+
console.log(`Serviço de template ${extra} excluído.`);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
179
363
|
if (group === "apps" && action === "create" && resourceId) {
|
|
180
364
|
const templateId = optionValue(args, "--template");
|
|
181
365
|
const regionCode = optionValue(args, "--region");
|
|
366
|
+
if (!templateId && optionValue(args, "--source")) {
|
|
367
|
+
const name = requireOption(args, "--name");
|
|
368
|
+
const source = requireOption(args, "--source").toUpperCase();
|
|
369
|
+
const repositoryUrl = optionValue(args, "--repository");
|
|
370
|
+
const gitConnectionId = optionValue(args, "--git-connection");
|
|
371
|
+
if (!new Set(["GITHUB", "GIT", "DOCKER", "DOCKERFILE"]).has(source)) {
|
|
372
|
+
throw new Error("--source deve ser github, git, docker ou dockerfile.");
|
|
373
|
+
}
|
|
374
|
+
if (source === "GITHUB" && !gitConnectionId) {
|
|
375
|
+
throw new Error("Aplicações GitHub requerem --git-connection com uma conexão já cadastrada no painel.");
|
|
376
|
+
}
|
|
377
|
+
if (["GITHUB", "GIT", "DOCKERFILE"].includes(source) && !repositoryUrl) {
|
|
378
|
+
throw new Error("Informe --repository para esta origem.");
|
|
379
|
+
}
|
|
380
|
+
const repositoryBranch = optionValue(args, "--branch");
|
|
381
|
+
const buildType = optionValue(args, "--build-type");
|
|
382
|
+
const description = optionValue(args, "--description");
|
|
383
|
+
const runtimePort = integerOption(args, "--port");
|
|
384
|
+
const response = await request(`/api/v1/projects/${encodeURIComponent(resourceId)}/apps`, {
|
|
385
|
+
method: "POST",
|
|
386
|
+
headers: {
|
|
387
|
+
"content-type": "application/json",
|
|
388
|
+
"idempotency-key": crypto.randomUUID(),
|
|
389
|
+
},
|
|
390
|
+
body: JSON.stringify({
|
|
391
|
+
name,
|
|
392
|
+
sourceType: source,
|
|
393
|
+
...(regionCode ? { regionCode } : {}),
|
|
394
|
+
...(repositoryUrl ? { repositoryUrl } : {}),
|
|
395
|
+
...(gitConnectionId ? { gitConnectionId } : {}),
|
|
396
|
+
...(repositoryBranch ? { repositoryBranch } : {}),
|
|
397
|
+
...(buildType ? { buildType: buildType.toUpperCase() } : {}),
|
|
398
|
+
...(description ? { description } : {}),
|
|
399
|
+
...(runtimePort ? { runtimePort } : {}),
|
|
400
|
+
}),
|
|
401
|
+
});
|
|
402
|
+
console.log(`Aplicação ${response.application.name} (${response.application.id}) criada: ${response.application.status}`);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
182
405
|
if (!templateId) {
|
|
183
406
|
throw new Error("Use --template <template-id>. Consulte os modelos com 'had templates list'.");
|
|
184
407
|
}
|
|
@@ -194,6 +417,47 @@ export async function main(args) {
|
|
|
194
417
|
}),
|
|
195
418
|
});
|
|
196
419
|
console.log(`Serviço ${response.service.name} (${response.service.id}) criado: ${response.service.status}`);
|
|
420
|
+
if (response.deployment) {
|
|
421
|
+
console.log(`Deployment do template: ${response.deployment.id} (${response.deployment.status})`);
|
|
422
|
+
console.log(`Status: had templates status ${resourceId} ${response.service.id} ${response.deployment.id}`);
|
|
423
|
+
console.log(`Logs: had templates logs ${resourceId} ${response.service.id} ${response.deployment.id}`);
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
console.log(`O provider ainda está registrando a execução. Consulte com: had templates deployments ${resourceId} ${response.service.id}`);
|
|
427
|
+
}
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (group === "apps" && action === "edit" && resourceId && extra) {
|
|
431
|
+
const name = optionValue(args, "--name");
|
|
432
|
+
const description = optionValue(args, "--description");
|
|
433
|
+
const repositoryBranch = optionValue(args, "--branch");
|
|
434
|
+
const buildType = optionValue(args, "--build-type");
|
|
435
|
+
const runtimePort = integerOption(args, "--port");
|
|
436
|
+
if (!name &&
|
|
437
|
+
!description &&
|
|
438
|
+
!repositoryBranch &&
|
|
439
|
+
!buildType &&
|
|
440
|
+
!runtimePort) {
|
|
441
|
+
throw new Error("Informe ao menos um campo para atualizar a aplicação.");
|
|
442
|
+
}
|
|
443
|
+
const response = await request(`/api/v1/projects/${encodeURIComponent(resourceId)}/apps/${encodeURIComponent(extra)}`, {
|
|
444
|
+
method: "PATCH",
|
|
445
|
+
headers: { "content-type": "application/json" },
|
|
446
|
+
body: JSON.stringify({
|
|
447
|
+
...(name ? { name } : {}),
|
|
448
|
+
...(description ? { description } : {}),
|
|
449
|
+
...(repositoryBranch ? { repositoryBranch } : {}),
|
|
450
|
+
...(buildType ? { buildType: buildType.toUpperCase() } : {}),
|
|
451
|
+
...(runtimePort ? { runtimePort } : {}),
|
|
452
|
+
}),
|
|
453
|
+
});
|
|
454
|
+
console.log(`Aplicação ${response.application.name} (${response.application.id}) atualizada.`);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (group === "apps" && action === "delete" && resourceId && extra) {
|
|
458
|
+
requireConfirmation(args);
|
|
459
|
+
await request(`/api/v1/projects/${encodeURIComponent(resourceId)}/apps/${encodeURIComponent(extra)}`, { method: "DELETE" });
|
|
460
|
+
console.log(`Aplicação ${extra} excluída.`);
|
|
197
461
|
return;
|
|
198
462
|
}
|
|
199
463
|
if (group === "deploy" && action && resourceId && !extra) {
|