kubiy-paas-cli 0.1.21 → 0.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.
- package/README.md +118 -118
- package/dist/index.js +2 -0
- package/dist/src/api.js +1 -0
- package/dist/src/commands/config.js +1 -0
- package/dist/src/commands/deploy.js +1 -0
- package/dist/src/commands/login.js +1 -0
- package/dist/src/commands/logs.js +1 -0
- package/dist/src/commands/rollback.js +1 -0
- package/dist/src/commands/service-get.js +1 -0
- package/dist/src/commands/service-link.js +1 -0
- package/dist/src/commands/version.js +1 -0
- package/dist/src/commands/whoami.js +1 -0
- package/dist/src/config.js +1 -0
- package/dist/src/git.js +1 -0
- package/dist/src/logger.js +1 -0
- package/package.json +36 -30
- package/index.js +0 -114
- package/src/api.js +0 -85
- package/src/commands/config.js +0 -71
- package/src/commands/deploy.js +0 -544
- package/src/commands/login.js +0 -16
- package/src/commands/logs.js +0 -112
- package/src/commands/rollback.js +0 -81
- package/src/commands/service-get.js +0 -39
- package/src/commands/service-link.js +0 -126
- package/src/commands/version.js +0 -17
- package/src/commands/whoami.js +0 -15
- package/src/config.js +0 -60
- package/src/git.js +0 -24
- package/src/logger.js +0 -31
package/src/commands/deploy.js
DELETED
|
@@ -1,544 +0,0 @@
|
|
|
1
|
-
// src/commands/deploy.js
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const path = require("path");
|
|
4
|
-
const os = require("os");
|
|
5
|
-
const https = require("https");
|
|
6
|
-
const tar = require("tar");
|
|
7
|
-
const { apiPost } = require("../api");
|
|
8
|
-
const { logInfo, logSuccess, logError } = require("../logger");
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Helpers para .kubiy.json
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
function loadProjectKubiyConfig() {
|
|
15
|
-
const projectRoot = process.cwd();
|
|
16
|
-
const configPath = path.join(projectRoot, ".kubiy.json");
|
|
17
|
-
|
|
18
|
-
if (!fs.existsSync(configPath)) {
|
|
19
|
-
throw new Error(
|
|
20
|
-
"No se encontró .kubiy.json en este proyecto.\n" +
|
|
21
|
-
"Ejecuta primero: kubiy link <serviceId>"
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const raw = fs.readFileSync(configPath, "utf8");
|
|
26
|
-
let json;
|
|
27
|
-
try {
|
|
28
|
-
json = JSON.parse(raw);
|
|
29
|
-
} catch (err) {
|
|
30
|
-
throw new Error(`.kubiy.json inválido: ${err.message}`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const { appId, serviceId, environment, apikey } = json;
|
|
34
|
-
var healthPath = json.healthPath || "";
|
|
35
|
-
|
|
36
|
-
if (!appId || !serviceId) {
|
|
37
|
-
throw new Error(
|
|
38
|
-
".kubiy.json incompleto. Debe tener: appId y serviceId.\n" +
|
|
39
|
-
JSON.stringify(json, null, 2)
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
appId,
|
|
45
|
-
serviceId,
|
|
46
|
-
environment,
|
|
47
|
-
healthPath,
|
|
48
|
-
apikey,
|
|
49
|
-
configPath,
|
|
50
|
-
config: json // Retornar el objeto completo para poder guardarlo después
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function saveProjectKubiyConfig(configPath, config) {
|
|
55
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Leer y parsear archivo .kubiyignore
|
|
60
|
-
* Retorna array de patrones a excluir
|
|
61
|
-
*/
|
|
62
|
-
function loadKubiyIgnore(projectRoot) {
|
|
63
|
-
const ignorePath = path.join(projectRoot, ".kubiyignore");
|
|
64
|
-
const defaultExcludes = [
|
|
65
|
-
"node_modules",
|
|
66
|
-
".git",
|
|
67
|
-
".env",
|
|
68
|
-
".DS_Store",
|
|
69
|
-
"*.tar",
|
|
70
|
-
"*.tar.gz",
|
|
71
|
-
"bin",
|
|
72
|
-
"obj",
|
|
73
|
-
"__pycache__",
|
|
74
|
-
"*.pyc",
|
|
75
|
-
"vendor",
|
|
76
|
-
];
|
|
77
|
-
|
|
78
|
-
if (!fs.existsSync(ignorePath)) {
|
|
79
|
-
return defaultExcludes;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
const content = fs.readFileSync(ignorePath, "utf8");
|
|
84
|
-
const lines = content
|
|
85
|
-
.split(/\r?\n/)
|
|
86
|
-
.map((line) => line.trim())
|
|
87
|
-
.filter((line) => line && !line.startsWith("#")); // Ignorar líneas vacías y comentarios
|
|
88
|
-
|
|
89
|
-
// Combinar con exclusiones por defecto y eliminar duplicados
|
|
90
|
-
const allExcludes = [...new Set([...defaultExcludes, ...lines])];
|
|
91
|
-
return allExcludes;
|
|
92
|
-
} catch (err) {
|
|
93
|
-
logInfo(`No se pudo leer .kubiyignore: ${err.message}. Usando exclusiones por defecto.`);
|
|
94
|
-
return defaultExcludes;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Convertir un patrón estilo glob simple (con `*` y `?`) a RegExp.
|
|
100
|
-
* Solo soporta `*` (cualquier secuencia sin `/`) y `?` (un caracter sin `/`).
|
|
101
|
-
*/
|
|
102
|
-
function globToRegex(pattern) {
|
|
103
|
-
const escaped = pattern
|
|
104
|
-
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
105
|
-
.replace(/\*/g, "[^/]*")
|
|
106
|
-
.replace(/\?/g, "[^/]");
|
|
107
|
-
return new RegExp(`^${escaped}$`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Normalizar patrones de exclusión: quitar ./ inicial y / final.
|
|
112
|
-
*/
|
|
113
|
-
function normalizeExcludePattern(pattern) {
|
|
114
|
-
let normalized = pattern.trim();
|
|
115
|
-
if (normalized.startsWith("./")) normalized = normalized.substring(2);
|
|
116
|
-
if (normalized.endsWith("/")) normalized = normalized.slice(0, -1);
|
|
117
|
-
return normalized;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Construir filtro para la API de `tar` a partir de la lista de patrones.
|
|
122
|
-
* Aplica la regla "si cualquier segmento del path coincide, se excluye",
|
|
123
|
-
* equivalente al comportamiento de `tar --exclude=` con nombres de carpetas
|
|
124
|
-
* o archivos a cualquier profundidad.
|
|
125
|
-
*/
|
|
126
|
-
function buildExcludeFilter(patterns) {
|
|
127
|
-
const regexes = patterns
|
|
128
|
-
.map(normalizeExcludePattern)
|
|
129
|
-
.filter(Boolean)
|
|
130
|
-
.map(globToRegex);
|
|
131
|
-
|
|
132
|
-
return function shouldInclude(filePath /* , stat */) {
|
|
133
|
-
const normalized = filePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
134
|
-
if (!normalized) return true;
|
|
135
|
-
const segments = normalized.split("/").filter(Boolean);
|
|
136
|
-
for (const re of regexes) {
|
|
137
|
-
if (segments.some((seg) => re.test(seg))) return false;
|
|
138
|
-
}
|
|
139
|
-
return true;
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Empaquetar código en .tar.gz
|
|
145
|
-
* - Se genera en el directorio temporal del sistema (NO dentro del repo)
|
|
146
|
-
* - Usa .kubiyignore para excluir archivos y carpetas
|
|
147
|
-
* - No depende de ningún binario del SO: usa el paquete `tar` de npm
|
|
148
|
-
*/
|
|
149
|
-
async function createTarball(deployType) {
|
|
150
|
-
const projectRoot = process.cwd();
|
|
151
|
-
// Si deployType es "server", usar nombre fijo deploy.tar.gz, sino usar timestamp
|
|
152
|
-
const archiveName = deployType === "server" ? "deploy.tar.gz" : `deploy-${Date.now()}.tar.gz`;
|
|
153
|
-
const archivePath = path.join(os.tmpdir(), archiveName);
|
|
154
|
-
|
|
155
|
-
logInfo("Empaquetando código en " + archiveName + " ...");
|
|
156
|
-
|
|
157
|
-
const excludes = loadKubiyIgnore(projectRoot);
|
|
158
|
-
const filter = buildExcludeFilter(excludes);
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
await tar.c(
|
|
162
|
-
{
|
|
163
|
-
gzip: true,
|
|
164
|
-
file: archivePath,
|
|
165
|
-
cwd: projectRoot,
|
|
166
|
-
portable: true,
|
|
167
|
-
filter,
|
|
168
|
-
},
|
|
169
|
-
["."]
|
|
170
|
-
);
|
|
171
|
-
} catch (err) {
|
|
172
|
-
throw new Error("Error al crear el .tar.gz: " + (err.message || String(err)));
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
logSuccess("Artefacto generado en temp: " + archivePath);
|
|
176
|
-
return archivePath;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Empaquetar directorio static en .tar.gz
|
|
181
|
-
* - Se genera en el directorio temporal del sistema
|
|
182
|
-
* - Empaqueta el contenido del directorio directamente (sin la carpeta contenedora)
|
|
183
|
-
* - No depende de ningún binario del SO: usa el paquete `tar` de npm
|
|
184
|
-
*/
|
|
185
|
-
async function createStaticTarball(staticDir) {
|
|
186
|
-
const archiveName = `static-deploy-${Date.now()}.tar.gz`;
|
|
187
|
-
const archivePath = path.join(os.tmpdir(), archiveName);
|
|
188
|
-
|
|
189
|
-
logInfo(`Empaquetando directorio static: ${staticDir} en ${archiveName}...`);
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
await tar.c(
|
|
193
|
-
{
|
|
194
|
-
gzip: true,
|
|
195
|
-
file: archivePath,
|
|
196
|
-
cwd: staticDir,
|
|
197
|
-
portable: true,
|
|
198
|
-
},
|
|
199
|
-
["."]
|
|
200
|
-
);
|
|
201
|
-
} catch (err) {
|
|
202
|
-
throw new Error(
|
|
203
|
-
"Error al crear el .tar.gz del directorio static: " + (err.message || String(err))
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
logSuccess("Artefacto static generado en temp: " + archivePath);
|
|
208
|
-
return archivePath;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Subir marker JSON a S3 con presigned URL (PUT)
|
|
213
|
-
*/
|
|
214
|
-
function uploadMarker(markerUploadUrl, deployId, releaseId) {
|
|
215
|
-
return new Promise((resolve, reject) => {
|
|
216
|
-
logInfo('markerUploadUrl: ' + markerUploadUrl);
|
|
217
|
-
const url = new URL(markerUploadUrl);
|
|
218
|
-
logInfo('url: ' + JSON.stringify(url));
|
|
219
|
-
const marker = {
|
|
220
|
-
deployId,
|
|
221
|
-
releaseId,
|
|
222
|
-
createdAt: new Date().toISOString(),
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
const payload = JSON.stringify(marker);
|
|
226
|
-
const options = {
|
|
227
|
-
method: "PUT",
|
|
228
|
-
hostname: url.hostname,
|
|
229
|
-
port: url.port || 443,
|
|
230
|
-
path: url.pathname + url.search,
|
|
231
|
-
headers: {
|
|
232
|
-
"Content-Type": "application/json",
|
|
233
|
-
"Content-Length": Buffer.byteLength(payload),
|
|
234
|
-
},
|
|
235
|
-
};
|
|
236
|
-
logInfo('options: ' + JSON.stringify(options));
|
|
237
|
-
|
|
238
|
-
const req = https.request(options, (res) => {
|
|
239
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
240
|
-
resolve();
|
|
241
|
-
} else {
|
|
242
|
-
let data = "";
|
|
243
|
-
res.on("data", (chunk) => (data += chunk));
|
|
244
|
-
res.on("end", () => {
|
|
245
|
-
reject(
|
|
246
|
-
new Error(
|
|
247
|
-
`Marker upload failed: HTTP ${res.statusCode} ${res.statusMessage} - ${data}`
|
|
248
|
-
)
|
|
249
|
-
);
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
req.on("error", reject);
|
|
255
|
-
req.write(payload);
|
|
256
|
-
req.end();
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Subir archivo a S3 con presigned URL (PUT)
|
|
262
|
-
*/
|
|
263
|
-
function uploadFileWithPresignedUrl(urlString, filePath) {
|
|
264
|
-
return new Promise((resolve, reject) => {
|
|
265
|
-
const url = new URL(urlString);
|
|
266
|
-
|
|
267
|
-
// Leer tamaño del archivo para evitar Transfer-Encoding: chunked
|
|
268
|
-
let stat;
|
|
269
|
-
try {
|
|
270
|
-
stat = fs.statSync(filePath);
|
|
271
|
-
} catch (err) {
|
|
272
|
-
return reject(new Error(`No se pudo leer el archivo a subir: ${err.message}`));
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const fileStream = fs.createReadStream(filePath);
|
|
276
|
-
fileStream.on("error", reject);
|
|
277
|
-
|
|
278
|
-
const options = {
|
|
279
|
-
method: "PUT",
|
|
280
|
-
hostname: url.hostname,
|
|
281
|
-
port: url.port || 443,
|
|
282
|
-
path: url.pathname + url.search,
|
|
283
|
-
headers: {
|
|
284
|
-
"Content-Type": "application/octet-stream",
|
|
285
|
-
"Content-Length": stat.size,
|
|
286
|
-
},
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
const req = https.request(options, (res) => {
|
|
290
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
291
|
-
resolve();
|
|
292
|
-
} else {
|
|
293
|
-
let data = "";
|
|
294
|
-
res.on("data", (chunk) => (data += chunk));
|
|
295
|
-
res.on("end", () => {
|
|
296
|
-
reject(
|
|
297
|
-
new Error(
|
|
298
|
-
`Error al subir a S3: HTTP ${res.statusCode} ${res.statusMessage} - ${data}`
|
|
299
|
-
)
|
|
300
|
-
);
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
req.on("error", reject);
|
|
306
|
-
|
|
307
|
-
fileStream.pipe(req);
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Seleccionar directorio de salida para deploy static
|
|
313
|
-
*/
|
|
314
|
-
function pickStaticOutDir(projectDir, overrideDir) {
|
|
315
|
-
if (overrideDir) {
|
|
316
|
-
const p = path.resolve(projectDir, overrideDir);
|
|
317
|
-
if (!fs.existsSync(p)) {
|
|
318
|
-
throw new Error(`Output dir not found: ${overrideDir}`);
|
|
319
|
-
}
|
|
320
|
-
return p;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const candidates = ["dist", "build", "out", path.join(".output", "public")];
|
|
324
|
-
for (const c of candidates) {
|
|
325
|
-
const p = path.resolve(projectDir, c);
|
|
326
|
-
if (fs.existsSync(p) && fs.statSync(p).isDirectory()) {
|
|
327
|
-
return p;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
throw new Error(
|
|
332
|
-
`No static output folder found. Expected one of: dist/, build/, out/, .output/public/. ` +
|
|
333
|
-
`Run your build first or specify --dir <folder>.`
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Verificar que el build static tenga index.html
|
|
339
|
-
*/
|
|
340
|
-
function assertStaticBuild(outDir) {
|
|
341
|
-
const indexPath = path.join(outDir, "index.html");
|
|
342
|
-
if (!fs.existsSync(indexPath)) {
|
|
343
|
-
throw new Error(`index.html not found in ${outDir}. Did you run the build?`);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Parsear argumentos del comando deploy
|
|
349
|
-
*/
|
|
350
|
-
function parseArgs(args) {
|
|
351
|
-
const opts = {
|
|
352
|
-
static: false,
|
|
353
|
-
dir: null,
|
|
354
|
-
message: null,
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
for (let i = 0; i < args.length; i++) {
|
|
358
|
-
const a = args[i];
|
|
359
|
-
if (a === "--static") {
|
|
360
|
-
opts.static = true;
|
|
361
|
-
} else if (a === "--dir" && i + 1 < args.length) {
|
|
362
|
-
opts.dir = args[i + 1];
|
|
363
|
-
i++;
|
|
364
|
-
} else if (a.startsWith("--dir=")) {
|
|
365
|
-
opts.dir = a.split("=")[1];
|
|
366
|
-
} else if (a === "--message" && i + 1 < args.length) {
|
|
367
|
-
opts.message = args[i + 1];
|
|
368
|
-
i++;
|
|
369
|
-
} else if (a.startsWith("--message=")) {
|
|
370
|
-
opts.message = a.slice("--message=".length);
|
|
371
|
-
} else if ((a === "-m") && i + 1 < args.length) {
|
|
372
|
-
opts.message = args[i + 1];
|
|
373
|
-
i++;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
return opts;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Comando principal: kubiy deploy
|
|
382
|
-
* Empaqueta el código y lo sube a S3
|
|
383
|
-
*/
|
|
384
|
-
|
|
385
|
-
async function run(args = []) {
|
|
386
|
-
let archivePath = null;
|
|
387
|
-
|
|
388
|
-
try {
|
|
389
|
-
const opts = parseArgs(args);
|
|
390
|
-
|
|
391
|
-
// 3) Cargar config del proyecto (.kubiy.json)
|
|
392
|
-
const kubiyConfig = loadProjectKubiyConfig();
|
|
393
|
-
const { appId, serviceId, environment, healthPath, apikey, configPath, config } = kubiyConfig;
|
|
394
|
-
|
|
395
|
-
// La autenticación se hace con el apikey de .kubiy.json (generado por `kubiy link`),
|
|
396
|
-
// no con el CLI token. Si falta el apikey, hay que re-linkear el proyecto.
|
|
397
|
-
if (!apikey) {
|
|
398
|
-
logError("No se encontró 'apikey' en .kubiy.json. Vuelve a linkear el proyecto:");
|
|
399
|
-
logError(" kubiy link <appId> --apikey=<tu-api-key>");
|
|
400
|
-
process.exit(1);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Validar que deployType coincida con el modo de deploy
|
|
404
|
-
const deployType = config.deployType;
|
|
405
|
-
if (!opts.static && deployType === "static") {
|
|
406
|
-
logError("Error: El proyecto está configurado como 'static' en .kubiy.json (deployType: static).");
|
|
407
|
-
logError("Para hacer deploy de este proyecto, usa: kubiy deploy --static");
|
|
408
|
-
process.exit(1);
|
|
409
|
-
}
|
|
410
|
-
if (opts.static && deployType && deployType !== "static") {
|
|
411
|
-
logError(`Error: El proyecto está configurado con deployType: '${deployType}' en .kubiy.json, pero estás intentando hacer deploy static.`);
|
|
412
|
-
logError("Para hacer deploy de este proyecto, usa: kubiy deploy (sin --static)");
|
|
413
|
-
process.exit(1);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Mensaje obligatorio que identifica el deploy (se envía como commit)
|
|
417
|
-
const commit = (opts.message && opts.message.trim()) ? opts.message.trim() : null;
|
|
418
|
-
if (!commit) {
|
|
419
|
-
logError("Debes indicar un mensaje que identifique este deploy.");
|
|
420
|
-
logError("Uso: kubiy deploy --message \"Descripción del deploy\"");
|
|
421
|
-
logError(" o: kubiy deploy -m \"Descripción del deploy\"");
|
|
422
|
-
logError(" o: kubiy deploy --message=Descripción");
|
|
423
|
-
process.exit(1);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// 4) Crear tarball (en temp)
|
|
427
|
-
if (opts.static) {
|
|
428
|
-
const projectRoot = process.cwd();
|
|
429
|
-
|
|
430
|
-
// Prioridad: 1) --dir del CLI, 2) static.outDir del config, 3) autodetección
|
|
431
|
-
const cliDir = opts.dir;
|
|
432
|
-
const configOutDir = config.static?.outDir || null;
|
|
433
|
-
|
|
434
|
-
const outDir = pickStaticOutDir(projectRoot, cliDir || configOutDir);
|
|
435
|
-
|
|
436
|
-
// Si se detectó automáticamente y no está en el config, guardarlo
|
|
437
|
-
if (!configOutDir && !cliDir) {
|
|
438
|
-
config.static = config.static || {};
|
|
439
|
-
const relativeOutDir = path.relative(projectRoot, outDir).replace(/\\/g, "/");
|
|
440
|
-
config.static.outDir = relativeOutDir;
|
|
441
|
-
saveProjectKubiyConfig(configPath, config);
|
|
442
|
-
logInfo(`Detected output dir: ${config.static.outDir} (saved)`);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
logInfo(`Usando directorio static: ${outDir}`);
|
|
446
|
-
assertStaticBuild(outDir);
|
|
447
|
-
archivePath = await createStaticTarball(outDir);
|
|
448
|
-
} else {
|
|
449
|
-
archivePath = await createTarball(deployType);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// 5) Pedir presigned URL a la API Kubiy
|
|
453
|
-
logInfo("Solicitando presigned URL a Kubiy...");
|
|
454
|
-
|
|
455
|
-
const presignBody = {
|
|
456
|
-
appId,
|
|
457
|
-
serviceId,
|
|
458
|
-
commit,
|
|
459
|
-
env: environment?.name,
|
|
460
|
-
healthPath: healthPath
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
// Si es static, agregar el parámetro static al body
|
|
464
|
-
if (opts.static) {
|
|
465
|
-
presignBody.static = true;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
const presignResp = await apiPost("/deploys/presign", presignBody, {
|
|
469
|
-
headers: {
|
|
470
|
-
"x-kubiy-token": apikey
|
|
471
|
-
}
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
//logInfo(JSON.stringify(presignResp, null, 2));
|
|
475
|
-
const { deployId, releaseId } = presignResp || {};
|
|
476
|
-
if (!deployId || !releaseId) {
|
|
477
|
-
throw new Error(
|
|
478
|
-
"Respuesta de /deploys/presign incompleta. Falta deployId o releaseId.\n" +
|
|
479
|
-
JSON.stringify(presignResp, null, 2)
|
|
480
|
-
);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
let key = presignResp?.key;
|
|
484
|
-
let uploadUrl = presignResp?.uploadUrl;
|
|
485
|
-
if (!key || !uploadUrl) {
|
|
486
|
-
throw new Error(
|
|
487
|
-
"Respuesta de /deploys/presign incompleta. Falta key o uploadUrl.\n" +
|
|
488
|
-
JSON.stringify(presignResp, null, 2)
|
|
489
|
-
);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
logSuccess(`Presign OK. deployId=${deployId} releaseId=${releaseId}`);
|
|
493
|
-
|
|
494
|
-
// 6) Subir a S3 usando uploadUrl si no es static
|
|
495
|
-
logInfo("Subiendo artefacto a S3...");
|
|
496
|
-
await uploadFileWithPresignedUrl(uploadUrl, archivePath);
|
|
497
|
-
logSuccess("Artefacto subido correctamente a S3.");
|
|
498
|
-
|
|
499
|
-
// 7) Avisar a Kubiy que inicie el deploy (POST /deploys)
|
|
500
|
-
// En deploys static no se llama a /deploys: la subida del artefacto al
|
|
501
|
-
// bucket presignado es suficiente para que el backend publique el sitio.
|
|
502
|
-
if (opts.static) {
|
|
503
|
-
logSuccess("🚀 Deploy static publicado correctamente en Kubiy.");
|
|
504
|
-
logInfo(`deployId=${deployId} releaseId=${releaseId}`);
|
|
505
|
-
} else {
|
|
506
|
-
logInfo("Notificando a Kubiy que inicie el deploy...");
|
|
507
|
-
|
|
508
|
-
const deployResp = await apiPost("/deploys", {
|
|
509
|
-
deployId,
|
|
510
|
-
appId,
|
|
511
|
-
serviceId,
|
|
512
|
-
artifactKey: key,
|
|
513
|
-
commit,
|
|
514
|
-
env: environment?.name
|
|
515
|
-
}, {
|
|
516
|
-
headers: {
|
|
517
|
-
"x-kubiy-token": apikey
|
|
518
|
-
}
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
logSuccess("🚀 Deploy iniciado correctamente en Kubiy.");
|
|
522
|
-
console.log(JSON.stringify(deployResp, null, 2));
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
logInfo(`Puedes consultar el servicio con: kubiy service:get ${serviceId}`);
|
|
526
|
-
logInfo(`Y los logs con: kubiy logs ${serviceId} --kind=deploy --lines=200`);
|
|
527
|
-
} catch (err) {
|
|
528
|
-
logError("Error en kubiy deploy:");
|
|
529
|
-
logError(err.message || err);
|
|
530
|
-
process.exit(1);
|
|
531
|
-
} finally {
|
|
532
|
-
// 8) Limpiar el artefacto local en temp
|
|
533
|
-
if (archivePath && fs.existsSync(archivePath)) {
|
|
534
|
-
try {
|
|
535
|
-
fs.unlinkSync(archivePath);
|
|
536
|
-
logInfo(`Artefacto temporal eliminado: ${archivePath}`);
|
|
537
|
-
} catch (e) {
|
|
538
|
-
logError(`No se pudo eliminar artefacto temporal: ${archivePath} (${e.message})`);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
module.exports = { run };
|
package/src/commands/login.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
const { setCliToken } = require("../config");
|
|
2
|
-
const { logSuccess, logError } = require("../logger");
|
|
3
|
-
|
|
4
|
-
async function run(args) {
|
|
5
|
-
const token = args[0];
|
|
6
|
-
|
|
7
|
-
if (!token) {
|
|
8
|
-
logError("Uso: kubiy login <cliToken>");
|
|
9
|
-
process.exit(1);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
setCliToken(token.trim());
|
|
13
|
-
logSuccess("CLI token guardado en ~/.kubiy/config.json");
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
module.exports = { run };
|
package/src/commands/logs.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
// src/commands/logs.js
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const path = require("path");
|
|
4
|
-
const { apiGet } = require("../api");
|
|
5
|
-
const { logInfo, logError } = require("../logger");
|
|
6
|
-
|
|
7
|
-
function parseArgs(args) {
|
|
8
|
-
const opts = {
|
|
9
|
-
kind: "app",
|
|
10
|
-
lines: 200,
|
|
11
|
-
};
|
|
12
|
-
const clean = [];
|
|
13
|
-
|
|
14
|
-
for (const a of args) {
|
|
15
|
-
if (a.startsWith("--kind=")) {
|
|
16
|
-
opts.kind = a.split("=")[1] || "app";
|
|
17
|
-
} else if (a.startsWith("--lines=")) {
|
|
18
|
-
const v = parseInt(a.split("=")[1] || "200", 10);
|
|
19
|
-
if (!isNaN(v) && v > 0) opts.lines = v;
|
|
20
|
-
} else {
|
|
21
|
-
clean.push(a);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// clean[0] puede o no tener serviceId
|
|
26
|
-
return { serviceId: clean[0] || null, opts };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function loadKubiyLink() {
|
|
30
|
-
const filepath = path.join(process.cwd(), ".kubiy.json");
|
|
31
|
-
|
|
32
|
-
if (!fs.existsSync(filepath)) {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const raw = fs.readFileSync(filepath, "utf8");
|
|
38
|
-
const cfg = JSON.parse(raw);
|
|
39
|
-
|
|
40
|
-
return { serviceId: cfg.serviceId || null, apikey: cfg.apikey || null };
|
|
41
|
-
} catch (err) {
|
|
42
|
-
// Si el json está roto, que el usuario se entere
|
|
43
|
-
logError("No se pudo leer .kubiy.json:", err.message || String(err));
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function run(args) {
|
|
49
|
-
const { serviceId: cliServiceId, opts } = parseArgs(args);
|
|
50
|
-
|
|
51
|
-
const linked = loadKubiyLink();
|
|
52
|
-
let serviceId = cliServiceId;
|
|
53
|
-
const apikey = linked && linked.apikey;
|
|
54
|
-
|
|
55
|
-
// 🍀 Si no viene serviceId por CLI, intentamos usar el del link
|
|
56
|
-
if (!serviceId) {
|
|
57
|
-
const linkedServiceId = linked && linked.serviceId;
|
|
58
|
-
if (!linkedServiceId) {
|
|
59
|
-
logError(
|
|
60
|
-
"Uso: kubiy logs <serviceId> --kind=deploy|app --lines=200\n" +
|
|
61
|
-
"O bien, ejecuta primero: kubiy link <serviceId> y luego solo 'kubiy logs'."
|
|
62
|
-
);
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
serviceId = linkedServiceId;
|
|
66
|
-
logInfo(`Usando serviceId del link (.kubiy.json): ${serviceId}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!apikey) {
|
|
70
|
-
logError("No se encontró 'apikey' en .kubiy.json. Vuelve a linkear el proyecto:");
|
|
71
|
-
logError(" kubiy link <appId> --apikey=<tu-api-key>");
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const kind = (opts.kind || "app").toLowerCase();
|
|
76
|
-
const lines = opts.lines || 200;
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
logInfo(
|
|
80
|
-
`Obteniendo logs (${kind}) del servicio ${serviceId} (últimas ${lines} líneas)...`
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const qs = new URLSearchParams({
|
|
84
|
-
kind,
|
|
85
|
-
lines: String(lines),
|
|
86
|
-
}).toString();
|
|
87
|
-
|
|
88
|
-
const pathUrl = `/services/${encodeURIComponent(serviceId)}/logs?${qs}`;
|
|
89
|
-
|
|
90
|
-
const resp = await apiGet(pathUrl, {
|
|
91
|
-
headers: { "x-kubiy-token": apikey },
|
|
92
|
-
}); // { stdout, stderr, ... }
|
|
93
|
-
const stdout = resp.stdout || "";
|
|
94
|
-
const stderr = resp.stderr || "";
|
|
95
|
-
|
|
96
|
-
if (stdout) {
|
|
97
|
-
process.stdout.write(stdout.endsWith("\n") ? stdout : stdout + "\n");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Si quieres mostrar stderr como comentario abajo:
|
|
101
|
-
if (stderr) {
|
|
102
|
-
console.error("\n[STDERR]");
|
|
103
|
-
process.stderr.write(stderr.endsWith("\n") ? stderr : stderr + "\n");
|
|
104
|
-
}
|
|
105
|
-
} catch (err) {
|
|
106
|
-
logError("Error al obtener logs:");
|
|
107
|
-
logError(err.message || String(err));
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
module.exports = { run };
|