kubiy-paas-cli 0.1.18 → 0.1.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kubiy-paas-cli",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "CLI para la plataforma PaaS de Kubiy",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -23,5 +23,8 @@
23
23
  "files": [
24
24
  "index.js",
25
25
  "src"
26
- ]
26
+ ],
27
+ "dependencies": {
28
+ "tar": "^7.4.3"
29
+ }
27
30
  }
@@ -1,12 +1,11 @@
1
1
  // src/commands/deploy.js
2
- const { execSync } = require("child_process");
3
2
  const fs = require("fs");
4
3
  const path = require("path");
5
4
  const os = require("os");
6
5
  const https = require("https");
6
+ const tar = require("tar");
7
7
  const { apiPost } = require("../api");
8
8
  const { logInfo, logSuccess, logError } = require("../logger");
9
- const { getCliToken } = require("../config");
10
9
 
11
10
  /**
12
11
  * Helpers para .kubiy.json
@@ -96,12 +95,58 @@ function loadKubiyIgnore(projectRoot) {
96
95
  }
97
96
  }
98
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
+
99
143
  /**
100
144
  * Empaquetar código en .tar.gz
101
145
  * - Se genera en el directorio temporal del sistema (NO dentro del repo)
102
146
  * - Usa .kubiyignore para excluir archivos y carpetas
147
+ * - No depende de ningún binario del SO: usa el paquete `tar` de npm
103
148
  */
104
- function createTarball(deployType) {
149
+ async function createTarball(deployType) {
105
150
  const projectRoot = process.cwd();
106
151
  // Si deployType es "server", usar nombre fijo deploy.tar.gz, sino usar timestamp
107
152
  const archiveName = deployType === "server" ? "deploy.tar.gz" : `deploy-${Date.now()}.tar.gz`;
@@ -109,43 +154,22 @@ function createTarball(deployType) {
109
154
 
110
155
  logInfo("Empaquetando código en " + archiveName + " ...");
111
156
 
112
- // Cargar patrones de exclusión desde .kubiyignore
113
157
  const excludes = loadKubiyIgnore(projectRoot);
114
-
115
- // Construir argumentos de exclusión para tar
116
- // Normalizar patrones: remover ./ inicial, agregar ./ si no tiene
117
- const excludeArgs = excludes.map((pattern) => {
118
- let normalized = pattern.trim();
119
- // Remover ./ inicial si existe
120
- if (normalized.startsWith("./")) {
121
- normalized = normalized.substring(2);
122
- }
123
- // Remover / final si existe (carpetas)
124
- if (normalized.endsWith("/")) {
125
- normalized = normalized.slice(0, -1);
126
- }
127
- return `--exclude=./${normalized}`;
128
- });
129
-
130
- const tarArgs = [
131
- "-czf",
132
- `"${archivePath}"`,
133
- ...excludeArgs,
134
- ".",
135
- ];
158
+ const filter = buildExcludeFilter(excludes);
136
159
 
137
160
  try {
138
- execSync(`tar ${tarArgs.join(" ")}`, {
139
- cwd: projectRoot,
140
- stdio: ["ignore", "pipe", "pipe"],
141
- encoding: "utf8",
142
- });
143
- } catch (err) {
144
- const stderr = err.stderr ? String(err.stderr) : "";
145
- throw new Error(
146
- "Error al crear el .tar.gz (¿tienes 'tar' instalado?).\n" +
147
- (stderr || err.message)
161
+ await tar.c(
162
+ {
163
+ gzip: true,
164
+ file: archivePath,
165
+ cwd: projectRoot,
166
+ portable: true,
167
+ filter,
168
+ },
169
+ ["."]
148
170
  );
171
+ } catch (err) {
172
+ throw new Error("Error al crear el .tar.gz: " + (err.message || String(err)));
149
173
  }
150
174
 
151
175
  logSuccess("Artefacto generado en temp: " + archivePath);
@@ -156,32 +180,27 @@ function createTarball(deployType) {
156
180
  * Empaquetar directorio static en .tar.gz
157
181
  * - Se genera en el directorio temporal del sistema
158
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
159
184
  */
160
- function createStaticTarball(staticDir) {
185
+ async function createStaticTarball(staticDir) {
161
186
  const archiveName = `static-deploy-${Date.now()}.tar.gz`;
162
187
  const archivePath = path.join(os.tmpdir(), archiveName);
163
188
 
164
189
  logInfo(`Empaquetando directorio static: ${staticDir} en ${archiveName}...`);
165
190
 
166
- // Cambiar al directorio static y empaquetar su contenido directamente
167
- const tarArgs = [
168
- "-czf",
169
- `"${archivePath}"`,
170
- "-C",
171
- `"${staticDir}"`,
172
- ".",
173
- ];
174
-
175
191
  try {
176
- execSync(`tar ${tarArgs.join(" ")}`, {
177
- stdio: ["ignore", "pipe", "pipe"],
178
- encoding: "utf8",
179
- });
192
+ await tar.c(
193
+ {
194
+ gzip: true,
195
+ file: archivePath,
196
+ cwd: staticDir,
197
+ portable: true,
198
+ },
199
+ ["."]
200
+ );
180
201
  } catch (err) {
181
- const stderr = err.stderr ? String(err.stderr) : "";
182
202
  throw new Error(
183
- "Error al crear el .tar.gz del directorio static (¿tienes 'tar' instalado?).\n" +
184
- (stderr || err.message)
203
+ "Error al crear el .tar.gz del directorio static: " + (err.message || String(err))
185
204
  );
186
205
  }
187
206
 
@@ -368,16 +387,19 @@ async function run(args = []) {
368
387
 
369
388
  try {
370
389
  const opts = parseArgs(args);
371
- const token = getCliToken();
372
- if (!token) {
373
- logError("No hay CLI token. Ejecuta: kubiy login <cliToken>");
374
- process.exit(1);
375
- }
376
390
 
377
391
  // 3) Cargar config del proyecto (.kubiy.json)
378
392
  const kubiyConfig = loadProjectKubiyConfig();
379
393
  const { appId, serviceId, environment, healthPath, apikey, configPath, config } = kubiyConfig;
380
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
+
381
403
  // Validar que deployType coincida con el modo de deploy
382
404
  const deployType = config.deployType;
383
405
  if (!opts.static && deployType === "static") {
@@ -422,9 +444,9 @@ async function run(args = []) {
422
444
 
423
445
  logInfo(`Usando directorio static: ${outDir}`);
424
446
  assertStaticBuild(outDir);
425
- archivePath = createStaticTarball(outDir);
447
+ archivePath = await createStaticTarball(outDir);
426
448
  } else {
427
- archivePath = createTarball(deployType);
449
+ archivePath = await createTarball(deployType);
428
450
  }
429
451
 
430
452
  // 5) Pedir presigned URL a la API Kubiy
@@ -2,7 +2,6 @@
2
2
  const fs = require("fs");
3
3
  const path = require("path");
4
4
  const { apiGet } = require("../api");
5
- const { getCliToken } = require("../config");
6
5
  const { logInfo, logError } = require("../logger");
7
6
 
8
7
  function parseArgs(args) {
@@ -27,7 +26,7 @@ function parseArgs(args) {
27
26
  return { serviceId: clean[0] || null, opts };
28
27
  }
29
28
 
30
- function resolveServiceIdFromLink() {
29
+ function loadKubiyLink() {
31
30
  const filepath = path.join(process.cwd(), ".kubiy.json");
32
31
 
33
32
  if (!fs.existsSync(filepath)) {
@@ -38,7 +37,7 @@ function resolveServiceIdFromLink() {
38
37
  const raw = fs.readFileSync(filepath, "utf8");
39
38
  const cfg = JSON.parse(raw);
40
39
 
41
- return cfg.serviceId || null;
40
+ return { serviceId: cfg.serviceId || null, apikey: cfg.apikey || null };
42
41
  } catch (err) {
43
42
  // Si el json está roto, que el usuario se entere
44
43
  logError("No se pudo leer .kubiy.json:", err.message || String(err));
@@ -49,11 +48,13 @@ function resolveServiceIdFromLink() {
49
48
  async function run(args) {
50
49
  const { serviceId: cliServiceId, opts } = parseArgs(args);
51
50
 
51
+ const linked = loadKubiyLink();
52
52
  let serviceId = cliServiceId;
53
+ const apikey = linked && linked.apikey;
53
54
 
54
55
  // 🍀 Si no viene serviceId por CLI, intentamos usar el del link
55
56
  if (!serviceId) {
56
- const linkedServiceId = resolveServiceIdFromLink();
57
+ const linkedServiceId = linked && linked.serviceId;
57
58
  if (!linkedServiceId) {
58
59
  logError(
59
60
  "Uso: kubiy logs <serviceId> --kind=deploy|app --lines=200\n" +
@@ -65,9 +66,9 @@ async function run(args) {
65
66
  logInfo(`Usando serviceId del link (.kubiy.json): ${serviceId}`);
66
67
  }
67
68
 
68
- const token = getCliToken();
69
- if (!token) {
70
- logError("No hay CLI token. Ejecuta: kubiy login <cliToken>");
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>");
71
72
  process.exit(1);
72
73
  }
73
74
 
@@ -86,7 +87,9 @@ async function run(args) {
86
87
 
87
88
  const pathUrl = `/services/${encodeURIComponent(serviceId)}/logs?${qs}`;
88
89
 
89
- const resp = await apiGet(pathUrl); // { stdout, stderr, ... }
90
+ const resp = await apiGet(pathUrl, {
91
+ headers: { "x-kubiy-token": apikey },
92
+ }); // { stdout, stderr, ... }
90
93
  const stdout = resp.stdout || "";
91
94
  const stderr = resp.stderr || "";
92
95
 
@@ -2,7 +2,6 @@
2
2
  const path = require("path");
3
3
  const fs = require("fs");
4
4
  const { apiPost } = require("../api");
5
- const { getCliToken } = require("../config");
6
5
  const { logInfo, logError, logSuccess } = require("../logger");
7
6
 
8
7
  function loadProjectKubiyConfig() {
@@ -10,7 +9,9 @@ function loadProjectKubiyConfig() {
10
9
  if (!fs.existsSync(configPath)) return null;
11
10
  try {
12
11
  const json = JSON.parse(fs.readFileSync(configPath, "utf8"));
13
- return json.appId && json.serviceId ? { appId: json.appId, serviceId: json.serviceId } : null;
12
+ return json.appId && json.serviceId
13
+ ? { appId: json.appId, serviceId: json.serviceId, apikey: json.apikey }
14
+ : null;
14
15
  } catch {
15
16
  return null;
16
17
  }
@@ -22,6 +23,7 @@ async function run(args) {
22
23
  const appId = args.length === 3 ? args[0] : (linked && linked.appId);
23
24
  const serviceId = args.length === 3 ? args[1] : (linked && linked.serviceId);
24
25
  const deployId = args.length === 3 ? args[2] : args[0];
26
+ const apikey = linked && linked.apikey;
25
27
 
26
28
  if (!deployId) {
27
29
  logError("Uso: kubiy rollback <deployId>\n kubiy rollback <appId> <serviceId> <deployId>\nO ejecuta desde un proyecto con kubiy link y pasa <deployId>.");
@@ -38,9 +40,9 @@ async function run(args) {
38
40
  process.exit(1);
39
41
  }
40
42
 
41
- const token = getCliToken();
42
- if (!token) {
43
- logError("No hay CLI token. Ejecuta: kubiy login <cliToken>");
43
+ if (!apikey) {
44
+ logError("No se encontró 'apikey' en .kubiy.json. Vuelve a linkear el proyecto:");
45
+ logError(" kubiy link <appId> --apikey=<tu-api-key>");
44
46
  process.exit(1);
45
47
  }
46
48
 
@@ -50,7 +52,7 @@ async function run(args) {
50
52
  const resp = await apiPost(
51
53
  `/rollback`,
52
54
  { appId, serviceId, deployId },
53
- { headers: { "X-Kubiy-Token": token } }
55
+ { headers: { "x-kubiy-token": apikey } }
54
56
  );
55
57
  logSuccess("Rollback enviado a Kubiy.");
56
58
 
@@ -1,6 +1,18 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
1
3
  const { apiGet } = require("../api");
2
4
  const { logError, logSuccess } = require("../logger");
3
- const { getCliToken } = require("../config");
5
+
6
+ function loadApiKeyFromLink() {
7
+ const filepath = path.join(process.cwd(), ".kubiy.json");
8
+ if (!fs.existsSync(filepath)) return null;
9
+ try {
10
+ const cfg = JSON.parse(fs.readFileSync(filepath, "utf8"));
11
+ return cfg.apikey || null;
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
4
16
 
5
17
  async function run(args) {
6
18
  const serviceId = args[0];
@@ -10,13 +22,16 @@ async function run(args) {
10
22
  process.exit(1);
11
23
  }
12
24
 
13
- const token = getCliToken();
14
- if (!token) {
15
- logError("No hay CLI token. Ejecuta: kubiy login <cliToken>");
25
+ const apikey = loadApiKeyFromLink();
26
+ if (!apikey) {
27
+ logError("No se encontró 'apikey' en .kubiy.json. Vuelve a linkear el proyecto:");
28
+ logError(" kubiy link <appId> --apikey=<tu-api-key>");
16
29
  process.exit(1);
17
30
  }
18
31
 
19
- const service = await apiGet(`/services/${serviceId}`);
32
+ const service = await apiGet(`/services/${serviceId}`, {
33
+ headers: { "x-kubiy-token": apikey },
34
+ });
20
35
  logSuccess("Servicio obtenido:");
21
36
  console.log(JSON.stringify(service, null, 2));
22
37
  }