kubiy-paas-cli 0.1.0 → 0.1.2
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/fix-link.js +62 -40
- package/index.js +44 -66
- package/package.json +5 -6
- package/src/api.js +79 -0
- package/src/commands/deploy.js +206 -0
- package/src/commands/git-connect.js +113 -0
- package/src/commands/login.js +16 -0
- package/src/commands/service-get.js +24 -0
- package/src/commands/version.js +17 -0
- package/src/commands/whoami.js +15 -0
- package/src/config.js +47 -0
- package/src/git.js +24 -0
- package/src/logger.js +31 -0
- package/src/ssh-config.js +51 -0
- package/src/ssh.js +40 -0
package/fix-link.js
CHANGED
|
@@ -4,47 +4,69 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
7
|
+
const isWin = process.platform === 'win32';
|
|
8
|
+
|
|
9
|
+
if (isWin) {
|
|
10
|
+
// ==========================
|
|
11
|
+
// LADO WINDOWS (lo que ya tenías)
|
|
12
|
+
// ==========================
|
|
13
|
+
|
|
14
|
+
// Ruta donde npm instala los comandos globales (típico en Windows)
|
|
15
|
+
const npmBinPath = path.join(os.homedir(), 'AppData', 'Roaming', 'npm');
|
|
16
|
+
const kubiyCmdPath = path.join(npmBinPath, 'kubiy.cmd');
|
|
17
|
+
const kubiyPs1Path = path.join(npmBinPath, 'kubiy.ps1');
|
|
18
|
+
|
|
19
|
+
// Corregir kubiy.cmd
|
|
20
|
+
if (fs.existsSync(kubiyCmdPath)) {
|
|
21
|
+
let content = fs.readFileSync(kubiyCmdPath, 'utf8');
|
|
22
|
+
|
|
23
|
+
// Verificar si necesita corrección (no tiene 'node' explícito)
|
|
24
|
+
if (!content.includes('node "%dp0%')) {
|
|
25
|
+
// Reemplazar la línea que ejecuta el .js directamente
|
|
26
|
+
content = content.replace(
|
|
27
|
+
/"%dp0%\\node_modules\\kubiy-paas-cli\\index\.js"/g,
|
|
28
|
+
'node "%dp0%\\node_modules\\kubiy-paas-cli\\index.js"'
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
fs.writeFileSync(kubiyCmdPath, content, 'utf8');
|
|
32
|
+
console.log('✅ Archivo kubiy.cmd corregido');
|
|
33
|
+
}
|
|
26
34
|
}
|
|
27
|
-
}
|
|
28
35
|
|
|
29
|
-
// Corregir kubiy.ps1
|
|
30
|
-
if (fs.existsSync(kubiyPs1Path)) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
36
|
+
// Corregir kubiy.ps1
|
|
37
|
+
if (fs.existsSync(kubiyPs1Path)) {
|
|
38
|
+
let content = fs.readFileSync(kubiyPs1Path, 'utf8');
|
|
39
|
+
|
|
40
|
+
// Verificar si necesita corrección (no tiene 'node' explícito)
|
|
41
|
+
if (!content.includes('node "$basedir')) {
|
|
42
|
+
// Reemplazar las líneas que ejecutan el .js directamente
|
|
43
|
+
content = content.replace(
|
|
44
|
+
/& "`\$basedir\/node_modules\/kubiy-paas-cli\/index\.js"/g,
|
|
45
|
+
'& node "$basedir/node_modules/kubiy-paas-cli/index.js"'
|
|
46
|
+
);
|
|
47
|
+
// También corregir la versión sin backticks (por si acaso)
|
|
48
|
+
content = content.replace(
|
|
49
|
+
/& "\$basedir\/node_modules\/kubiy-paas-cli\/index\.js"/g,
|
|
50
|
+
'& node "$basedir/node_modules/kubiy-paas-cli/index.js"'
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
fs.writeFileSync(kubiyPs1Path, content, 'utf8');
|
|
54
|
+
console.log('✅ Archivo kubiy.ps1 corregido');
|
|
55
|
+
}
|
|
48
56
|
}
|
|
49
|
-
}
|
|
50
57
|
|
|
58
|
+
} else {
|
|
59
|
+
// ==========================
|
|
60
|
+
// LADO UNIX / MAC
|
|
61
|
+
// ==========================
|
|
62
|
+
try {
|
|
63
|
+
const indexPath = path.join(__dirname, 'index.js');
|
|
64
|
+
|
|
65
|
+
if (fs.existsSync(indexPath)) {
|
|
66
|
+
fs.chmodSync(indexPath, 0o755);
|
|
67
|
+
console.log('✅ Unix: permisos ejecutables aplicados a index.js (chmod +x)');
|
|
68
|
+
}
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.log('⚠ No se pudo aplicar chmod a index.js en Unix:', e.message);
|
|
71
|
+
}
|
|
72
|
+
}
|
package/index.js
CHANGED
|
@@ -1,77 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// CLI minimalista
|
|
4
|
-
const fs = require("fs");
|
|
5
|
-
const os = require("os");
|
|
6
3
|
const path = require("path");
|
|
4
|
+
const { logInfo, logError } = require("./src/logger");
|
|
7
5
|
|
|
8
|
-
const args = process.argv.slice(2);
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const command = args[0];
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
9
|
+
async function main() {
|
|
10
|
+
try {
|
|
11
|
+
switch (command) {
|
|
12
|
+
case "login":
|
|
13
|
+
await require("./src/commands/login").run(args.slice(1));
|
|
14
|
+
break;
|
|
15
|
+
case "whoami":
|
|
16
|
+
await require("./src/commands/whoami").run(args.slice(1));
|
|
17
|
+
break;
|
|
18
|
+
case "service:get":
|
|
19
|
+
await require("./src/commands/service-get").run(args.slice(1));
|
|
20
|
+
break;
|
|
21
|
+
case "git:connect":
|
|
22
|
+
await require("./src/commands/git-connect").run(args.slice(1));
|
|
23
|
+
break;
|
|
24
|
+
case "deploy":
|
|
25
|
+
await require("./src/commands/deploy").run();
|
|
26
|
+
break;
|
|
27
|
+
case "version":
|
|
28
|
+
case "--version":
|
|
29
|
+
case "-v":
|
|
30
|
+
await require("./src/commands/version").run(args.slice(1));
|
|
31
|
+
break;
|
|
32
|
+
default:
|
|
33
|
+
printHelp();
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
logError("Error en CLI:", err.message || err);
|
|
36
38
|
process.exit(1);
|
|
37
39
|
}
|
|
38
|
-
|
|
39
|
-
const cfg = readConfig();
|
|
40
|
-
cfg.cliToken = token.trim();
|
|
41
|
-
writeConfig(cfg);
|
|
42
|
-
|
|
43
|
-
console.log("✅ CLI token guardado en ~/.kubiy/config.json");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function cmdWhoami() {
|
|
47
|
-
const cfg = readConfig();
|
|
48
|
-
if (!cfg.cliToken) {
|
|
49
|
-
console.log("No hay token configurado. Ejecuta: kubiy login <cliToken>");
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
console.log("CLI configurado. Token (truncado):", cfg.cliToken.slice(0, 6) + "****");
|
|
53
40
|
}
|
|
54
41
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
console.log("Kubiy PaaS CLI");
|
|
67
|
-
console.log("Uso:");
|
|
68
|
-
console.log(" kubiy login <cliToken>");
|
|
69
|
-
console.log(" kubiy whoami");
|
|
70
|
-
process.exit(0);
|
|
71
|
-
}
|
|
42
|
+
function printHelp() {
|
|
43
|
+
logInfo("Kubiy PaaS CLI");
|
|
44
|
+
console.log("");
|
|
45
|
+
console.log("Uso:");
|
|
46
|
+
console.log(" kubiy login <cliToken>");
|
|
47
|
+
console.log(" kubiy whoami");
|
|
48
|
+
console.log(" kubiy service:get <serviceId>");
|
|
49
|
+
console.log(" kubiy git:connect <serviceId>");
|
|
50
|
+
console.log(" kubiy deploy");
|
|
51
|
+
console.log(" kubiy version");
|
|
52
|
+
console.log("");
|
|
72
53
|
}
|
|
73
54
|
|
|
74
|
-
main()
|
|
75
|
-
console.error("Error en CLI:", err);
|
|
76
|
-
process.exit(1);
|
|
77
|
-
});
|
|
55
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kubiy-paas-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "CLI para
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "CLI para la plataforma PaaS de Kubiy",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"kubiy": "index.js"
|
|
8
8
|
},
|
|
9
|
-
"preferGlobal": true,
|
|
10
9
|
"scripts": {
|
|
11
|
-
"start": "node index.js"
|
|
12
|
-
"postinstall": "node fix-link.js"
|
|
10
|
+
"start": "node index.js"
|
|
13
11
|
},
|
|
14
12
|
"keywords": [
|
|
15
13
|
"kubiy",
|
|
@@ -17,9 +15,10 @@
|
|
|
17
15
|
"cli",
|
|
18
16
|
"deploy"
|
|
19
17
|
],
|
|
20
|
-
"author": "
|
|
18
|
+
"author": "Kubiy",
|
|
21
19
|
"license": "MIT",
|
|
22
20
|
"engines": {
|
|
23
21
|
"node": ">=12.0.0"
|
|
24
22
|
}
|
|
25
23
|
}
|
|
24
|
+
|
package/src/api.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const https = require("https");
|
|
2
|
+
const { getCliToken } = require("./config");
|
|
3
|
+
const { logError } = require("./logger");
|
|
4
|
+
|
|
5
|
+
// Ajusta esta URL a tu API real
|
|
6
|
+
const API_BASE = process.env.KUBIY_API_BASE || "https://i5c812hwh2.execute-api.us-east-1.amazonaws.com";
|
|
7
|
+
|
|
8
|
+
function request(method, path, body, opts = {}) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const token = opts.token || getCliToken();
|
|
11
|
+
const url = new URL(path, API_BASE);
|
|
12
|
+
|
|
13
|
+
const headers = {
|
|
14
|
+
"Accept": "application/json"
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
let payload = null;
|
|
18
|
+
if (body) {
|
|
19
|
+
payload = JSON.stringify(body);
|
|
20
|
+
headers["Content-Type"] = "application/json";
|
|
21
|
+
headers["Content-Length"] = Buffer.byteLength(payload);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (token) {
|
|
25
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const options = {
|
|
29
|
+
method,
|
|
30
|
+
headers
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const req = https.request(url, options, (res) => {
|
|
34
|
+
let data = "";
|
|
35
|
+
|
|
36
|
+
res.on("data", (chunk) => (data += chunk));
|
|
37
|
+
res.on("end", () => {
|
|
38
|
+
if (!data) {
|
|
39
|
+
return resolve(null);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
43
|
+
try {
|
|
44
|
+
resolve(JSON.parse(data));
|
|
45
|
+
} catch (e) {
|
|
46
|
+
resolve(data);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
reject(
|
|
50
|
+
new Error(
|
|
51
|
+
`API ${res.statusCode}: ${data || res.statusMessage || "Error"}`
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
req.on("error", reject);
|
|
59
|
+
|
|
60
|
+
if (payload) {
|
|
61
|
+
req.write(payload);
|
|
62
|
+
}
|
|
63
|
+
req.end();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function apiGet(path, opts) {
|
|
68
|
+
return request("GET", path, null, opts);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function apiPost(path, body, opts) {
|
|
72
|
+
return request("POST", path, body, opts);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
apiGet,
|
|
77
|
+
apiPost,
|
|
78
|
+
API_BASE
|
|
79
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// src/commands/deploy.js
|
|
2
|
+
const { execSync } = require("child_process");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { logInfo, logSuccess, logError } = require("../logger");
|
|
6
|
+
const { SSH_PRIV } = require("../ssh"); // ya lo usas en git:connect
|
|
7
|
+
|
|
8
|
+
function runGit(command, extraOptions = {}) {
|
|
9
|
+
const full = `git ${command}`;
|
|
10
|
+
try {
|
|
11
|
+
const out = execSync(full, {
|
|
12
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
13
|
+
encoding: "utf8",
|
|
14
|
+
...extraOptions,
|
|
15
|
+
});
|
|
16
|
+
return out.trim();
|
|
17
|
+
} catch (err) {
|
|
18
|
+
const stderr = err.stderr ? String(err.stderr) : "";
|
|
19
|
+
throw new Error(`Error al ejecutar: ${full}\n${stderr || err.message}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isGitRepo() {
|
|
24
|
+
return fs.existsSync(path.join(process.cwd(), ".git"));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function ensureGitRepo() {
|
|
28
|
+
if (isGitRepo()) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
logInfo("Este directorio no es un repositorio git. Inicializando...");
|
|
33
|
+
runGit("init");
|
|
34
|
+
logSuccess("Repositorio git inicializado.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Intenta obtener la rama actual.
|
|
39
|
+
* Si no hay commits / HEAD está raro, devuelve null.
|
|
40
|
+
*/
|
|
41
|
+
function getCurrentBranch() {
|
|
42
|
+
try {
|
|
43
|
+
return runGit("symbolic-ref --short HEAD");
|
|
44
|
+
} catch (_) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function branchExists(branchName) {
|
|
50
|
+
try {
|
|
51
|
+
const out = runGit(`branch --list ${branchName}`);
|
|
52
|
+
return out !== "";
|
|
53
|
+
} catch (_) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Asegura que la rama activa sea master.
|
|
60
|
+
*
|
|
61
|
+
* Casos:
|
|
62
|
+
* - Si ya estamos en master → OK.
|
|
63
|
+
* - Si no hay rama actual (repo recién creado) → crea master.
|
|
64
|
+
* - Si hay otra rama → si existe master hacemos checkout, si no renombramos a master.
|
|
65
|
+
*/
|
|
66
|
+
function ensureOnMaster() {
|
|
67
|
+
let current = getCurrentBranch();
|
|
68
|
+
|
|
69
|
+
if (!current || current === "HEAD") {
|
|
70
|
+
logInfo("No hay rama actual válida. Creando / cambiando a 'master'...");
|
|
71
|
+
|
|
72
|
+
if (branchExists("master")) {
|
|
73
|
+
runGit("checkout master");
|
|
74
|
+
logSuccess("Ahora estás en master.");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Repo recién creado sin ramas reales → creamos master
|
|
79
|
+
runGit("checkout -b master");
|
|
80
|
+
logSuccess("Rama master creada y activada.");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (current === "master") {
|
|
85
|
+
logInfo("Ya estás en la rama master.");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
logInfo(`Rama actual: ${current}`);
|
|
90
|
+
|
|
91
|
+
if (branchExists("master")) {
|
|
92
|
+
logInfo("Encontrada rama master. Cambiando a master...");
|
|
93
|
+
runGit("checkout master");
|
|
94
|
+
logSuccess("Ahora estás en master.");
|
|
95
|
+
} else {
|
|
96
|
+
logInfo(`No existe rama master. Renombrando rama actual '${current}' a 'master'...`);
|
|
97
|
+
runGit("branch -m master");
|
|
98
|
+
logSuccess("Rama actual renombrada a master.");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function ensureKubiyRemote() {
|
|
103
|
+
try {
|
|
104
|
+
const url = runGit("remote get-url kubiy");
|
|
105
|
+
logInfo(`Remote 'kubiy' encontrado: ${url}`);
|
|
106
|
+
} catch (_) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
"No se encontró el remote 'kubiy'.\n" +
|
|
109
|
+
"Asegúrate de haber ejecutado antes:\n kubiy git:connect <serviceId>"
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function hasChangesToCommit() {
|
|
115
|
+
const status = runGit("status --porcelain");
|
|
116
|
+
return status !== "";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createAutoCommit() {
|
|
120
|
+
const msg = `kubiy deploy - ${new Date().toISOString()}`;
|
|
121
|
+
logInfo("Creando commit automático...");
|
|
122
|
+
|
|
123
|
+
runGit("add .");
|
|
124
|
+
try {
|
|
125
|
+
runGit(`commit -m "${msg}"`);
|
|
126
|
+
logSuccess(`Commit creado: ${msg}`);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
if (String(err.message).includes("nothing to commit")) {
|
|
129
|
+
logInfo("No hay cambios nuevos para commitear.");
|
|
130
|
+
} else {
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function pushToKubiy() {
|
|
137
|
+
logInfo("Haciendo push a 'kubiy' (rama master)...");
|
|
138
|
+
|
|
139
|
+
// Ruta normalizada para que ssh no se maree con backslashes
|
|
140
|
+
const identityPath = SSH_PRIV.replace(/\\/g, "/");
|
|
141
|
+
|
|
142
|
+
const env = {
|
|
143
|
+
...process.env,
|
|
144
|
+
// Forzar que git use ESTE ssh con ESTA llave y SIN validar host key
|
|
145
|
+
GIT_SSH_COMMAND: `ssh -i "${identityPath}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null`,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
runGit("push kubiy master", { env });
|
|
149
|
+
logSuccess("Push a kubiy master completado.");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function run() {
|
|
153
|
+
try {
|
|
154
|
+
// 1) Repo git
|
|
155
|
+
ensureGitRepo();
|
|
156
|
+
|
|
157
|
+
// 2) Estar en master sí o sí
|
|
158
|
+
ensureOnMaster();
|
|
159
|
+
|
|
160
|
+
// 3) Remote kubiy configurado
|
|
161
|
+
ensureKubiyRemote();
|
|
162
|
+
|
|
163
|
+
// 4) Commit automático si hay cambios
|
|
164
|
+
if (hasChangesToCommit()) {
|
|
165
|
+
createAutoCommit();
|
|
166
|
+
} else {
|
|
167
|
+
logInfo("No hay cambios locales. Solo se intentará hacer push.");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 5) Push
|
|
171
|
+
pushToKubiy();
|
|
172
|
+
|
|
173
|
+
logSuccess("🚀 Deploy completado. Revisa tu servicio Kubiy.");
|
|
174
|
+
} catch (err) {
|
|
175
|
+
const msg = String(err.message || "").toLowerCase();
|
|
176
|
+
logError("Error en kubiy deploy:");
|
|
177
|
+
|
|
178
|
+
// Caso clásico: el remoto tiene commits que el local no
|
|
179
|
+
if (
|
|
180
|
+
msg.includes("fetch first") ||
|
|
181
|
+
msg.includes("non-fast-forward") ||
|
|
182
|
+
msg.includes("failed to push some refs")
|
|
183
|
+
) {
|
|
184
|
+
logError("Tu rama local 'master' está detrás del remoto 'kubiy/master'.");
|
|
185
|
+
logError("Esto pasa cuando el servidor ya tiene commits que tu repo local no tiene.");
|
|
186
|
+
console.log("");
|
|
187
|
+
console.log("Para alinearlo, ejecuta UNA sola vez:");
|
|
188
|
+
console.log("");
|
|
189
|
+
console.log(" git fetch kubiy");
|
|
190
|
+
console.log(" git reset --hard kubiy/master");
|
|
191
|
+
console.log("");
|
|
192
|
+
console.log("Después de eso, vuelve a correr:");
|
|
193
|
+
console.log("");
|
|
194
|
+
console.log(" kubiy deploy");
|
|
195
|
+
} else {
|
|
196
|
+
// Otros errores, mostramos el mensaje crudo
|
|
197
|
+
logError(err.message);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
run,
|
|
206
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/commands/git-connect.js
|
|
2
|
+
const { getCliToken } = require("../config");
|
|
3
|
+
const { apiGet, apiPost } = require("../api");
|
|
4
|
+
const { ensureSshKey, SSH_PRIV } = require("../ssh");
|
|
5
|
+
const { ensureSshHostConfig } = require("../ssh-config");
|
|
6
|
+
const { addRemote } = require("../git");
|
|
7
|
+
const { logInfo, logSuccess, logError } = require("../logger");
|
|
8
|
+
|
|
9
|
+
const { execSync } = require("child_process");
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
|
|
12
|
+
function ensureGitRepo() {
|
|
13
|
+
if (fs.existsSync(".git")) return;
|
|
14
|
+
|
|
15
|
+
logInfo("Este directorio no es un repositorio git. Inicializando...");
|
|
16
|
+
execSync("git init", { stdio: "inherit" });
|
|
17
|
+
try {
|
|
18
|
+
execSync("git checkout -b main", { stdio: "inherit" });
|
|
19
|
+
} catch (_) {
|
|
20
|
+
// Si ya existe main/master, no pasa nada
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseRepoUrl(gitRepoUrl) {
|
|
25
|
+
// Esperamos algo como ssh://deploy@1.2.3.4/home/deploy/app.git
|
|
26
|
+
try {
|
|
27
|
+
const url = new URL(gitRepoUrl);
|
|
28
|
+
return {
|
|
29
|
+
host: url.hostname,
|
|
30
|
+
path: url.pathname // /home/deploy/app.git
|
|
31
|
+
};
|
|
32
|
+
} catch (e) {
|
|
33
|
+
// fallback muy básico
|
|
34
|
+
// ssh://deploy@1.2.3.4/home/deploy/app.git
|
|
35
|
+
if (!gitRepoUrl.startsWith("ssh://")) {
|
|
36
|
+
throw new Error(`gitRepoUrl inválido: ${gitRepoUrl}`);
|
|
37
|
+
}
|
|
38
|
+
const withoutScheme = gitRepoUrl.slice("ssh://".length);
|
|
39
|
+
const [userHost, pathPart] = withoutScheme.split("/", 2);
|
|
40
|
+
const host = userHost.split("@").pop();
|
|
41
|
+
return {
|
|
42
|
+
host,
|
|
43
|
+
path: "/" + pathPart
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function run(argv) {
|
|
49
|
+
const serviceId = argv[0];
|
|
50
|
+
|
|
51
|
+
if (!serviceId) {
|
|
52
|
+
logError("Uso: kubiy git:connect <serviceId>");
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const token = getCliToken();
|
|
57
|
+
if (!token) {
|
|
58
|
+
logError("No hay CLI token. Ejecuta: kubiy login <cliToken>");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 1) Asegurar llave SSH local (~/.kubiy/id_ed25519)
|
|
63
|
+
const pubKey = ensureSshKey();
|
|
64
|
+
|
|
65
|
+
// 2) Registrar llave en tu API
|
|
66
|
+
logInfo("Registrando llave SSH en Kubiy...");
|
|
67
|
+
await apiPost("/cli/ssh-keys", { publicKey: pubKey });
|
|
68
|
+
logSuccess("Llave SSH registrada.");
|
|
69
|
+
|
|
70
|
+
// 3) Sincronizar llaves con la EC2 del servicio
|
|
71
|
+
logInfo("Sincronizando llaves SSH con la instancia...");
|
|
72
|
+
try {
|
|
73
|
+
const syncResp = await apiPost(`/cli/services/${serviceId}/sync-ssh`, {});
|
|
74
|
+
logSuccess(
|
|
75
|
+
`Llaves sincronizadas. keysApplied=${syncResp.keysApplied}, ssmStatus=${syncResp.ssmStatus}`
|
|
76
|
+
);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
logError(`No se pudo sincronizar llaves con la instancia: ${e.message}`);
|
|
79
|
+
// seguimos, porque igual podrías tener la llave ya metida de antes
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 4) Obtener info del servicio (para gitRepoUrl)
|
|
83
|
+
logInfo("Obteniendo información del servicio...");
|
|
84
|
+
const service = await apiGet(`/services/${serviceId}`);
|
|
85
|
+
|
|
86
|
+
const gitRepoUrl = service.gitRepoUrl;
|
|
87
|
+
if (!gitRepoUrl) {
|
|
88
|
+
logError("El servicio no tiene gitRepoUrl todavía. Asegúrate que esté en RUNNING.");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const { host: ec2Host, path: repoPath } = parseRepoUrl(gitRepoUrl);
|
|
93
|
+
|
|
94
|
+
// 5) Configurar ~/.ssh/config con un host lógico único para este servicio
|
|
95
|
+
const hostAlias = `kubiy-${serviceId}`.slice(0, 60); // por si se va muy largo
|
|
96
|
+
|
|
97
|
+
ensureSshHostConfig(hostAlias, ec2Host, SSH_PRIV);
|
|
98
|
+
|
|
99
|
+
// 6) Configurar remote con el alias, no con la IP directa
|
|
100
|
+
ensureGitRepo();
|
|
101
|
+
logInfo("Configurando remote 'kubiy'...");
|
|
102
|
+
const remoteUrl = `ssh://${hostAlias}${repoPath}`;
|
|
103
|
+
addRemote("kubiy", remoteUrl);
|
|
104
|
+
|
|
105
|
+
logSuccess("Remote 'kubiy' configurado.");
|
|
106
|
+
|
|
107
|
+
logSuccess("Listo. Puedes hacer ahora:");
|
|
108
|
+
console.log(" git add .");
|
|
109
|
+
console.log(" git commit -m \"tu mensaje\"");
|
|
110
|
+
console.log(" git push kubiy main # o master");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { run };
|
|
@@ -0,0 +1,16 @@
|
|
|
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 };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const { apiGet } = require("../api");
|
|
2
|
+
const { logError, logSuccess } = require("../logger");
|
|
3
|
+
const { getCliToken } = require("../config");
|
|
4
|
+
|
|
5
|
+
async function run(args) {
|
|
6
|
+
const serviceId = args[0];
|
|
7
|
+
|
|
8
|
+
if (!serviceId) {
|
|
9
|
+
logError("Uso: kubiy service:get <serviceId>");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const token = getCliToken();
|
|
14
|
+
if (!token) {
|
|
15
|
+
logError("No hay CLI token. Ejecuta: kubiy login <cliToken>");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const service = await apiGet(`/services/${serviceId}`);
|
|
20
|
+
logSuccess("Servicio obtenido:");
|
|
21
|
+
console.log(JSON.stringify(service, null, 2));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { run };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const { logInfo } = require("../logger");
|
|
2
|
+
const { API_BASE, apiGet } = require("../api");
|
|
3
|
+
const pkg = require("../../package.json");
|
|
4
|
+
|
|
5
|
+
async function run() {
|
|
6
|
+
logInfo(`Kubiy CLI version: ${pkg.version}`);
|
|
7
|
+
logInfo(`API base: ${API_BASE}`);
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const status = await apiGet("/status"); // ajusta si no tienes este endpoint
|
|
11
|
+
console.log("API status:", status);
|
|
12
|
+
} catch (e) {
|
|
13
|
+
console.log("No se pudo consultar /status de la API:", e.message);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { run };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const { getCliToken } = require("../config");
|
|
2
|
+
const { logInfo, logWarn } = require("../logger");
|
|
3
|
+
|
|
4
|
+
async function run() {
|
|
5
|
+
const token = getCliToken();
|
|
6
|
+
if (!token) {
|
|
7
|
+
logWarn("No hay CLI token configurado. Ejecuta: kubiy login <cliToken>");
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
logInfo("CLI configurado.");
|
|
12
|
+
console.log("Token (truncado):", token.slice(0, 6) + "****");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = { run };
|
package/src/config.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), ".kubiy");
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
7
|
+
|
|
8
|
+
function ensureConfigDir() {
|
|
9
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
10
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readConfig() {
|
|
15
|
+
try {
|
|
16
|
+
if (!fs.existsSync(CONFIG_FILE)) return {};
|
|
17
|
+
const raw = fs.readFileSync(CONFIG_FILE, "utf8");
|
|
18
|
+
return JSON.parse(raw);
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function writeConfig(obj) {
|
|
25
|
+
ensureConfigDir();
|
|
26
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(obj, null, 2), "utf8");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getCliToken() {
|
|
30
|
+
const cfg = readConfig();
|
|
31
|
+
return cfg.cliToken;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function setCliToken(token) {
|
|
35
|
+
const cfg = readConfig();
|
|
36
|
+
cfg.cliToken = token;
|
|
37
|
+
writeConfig(cfg);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
CONFIG_DIR,
|
|
42
|
+
CONFIG_FILE,
|
|
43
|
+
readConfig,
|
|
44
|
+
writeConfig,
|
|
45
|
+
getCliToken,
|
|
46
|
+
setCliToken
|
|
47
|
+
};
|
package/src/git.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const { execSync } = require("child_process");
|
|
2
|
+
const { logInfo, logSuccess, logWarn } = require("./logger");
|
|
3
|
+
|
|
4
|
+
function run(cmd) {
|
|
5
|
+
execSync(cmd, { stdio: "inherit" });
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function addRemote(name, url) {
|
|
9
|
+
try {
|
|
10
|
+
// quitar si existe
|
|
11
|
+
execSync(`git remote remove ${name}`, { stdio: "ignore" });
|
|
12
|
+
} catch (_) {
|
|
13
|
+
// no pasa nada si no existe
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
logInfo(`Agregando remote '${name}' -> ${url}`);
|
|
17
|
+
execSync(`git remote add ${name} "${url}"`, { stdio: "inherit" });
|
|
18
|
+
logSuccess(`Remote '${name}' configurado.`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
run,
|
|
23
|
+
addRemote
|
|
24
|
+
};
|
package/src/logger.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const COLORS = {
|
|
2
|
+
reset: "\x1b[0m",
|
|
3
|
+
green: "\x1b[32m",
|
|
4
|
+
yellow: "\x1b[33m",
|
|
5
|
+
red: "\x1b[31m",
|
|
6
|
+
cyan: "\x1b[36m"
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function logInfo(...args) {
|
|
10
|
+
console.log(COLORS.cyan + "ℹ" + COLORS.reset, ...args);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function logSuccess(...args) {
|
|
14
|
+
console.log(COLORS.green + "✔" + COLORS.reset, ...args);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function logWarn(...args) {
|
|
18
|
+
console.log(COLORS.yellow + "⚠" + COLORS.reset, ...args);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function logError(...args) {
|
|
22
|
+
console.error(COLORS.red + "✖" + COLORS.reset, ...args);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
logInfo,
|
|
27
|
+
logSuccess,
|
|
28
|
+
logWarn,
|
|
29
|
+
logError
|
|
30
|
+
};
|
|
31
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// src/ssh-config.js
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { logInfo, logSuccess } = require("./logger");
|
|
6
|
+
|
|
7
|
+
const SSH_DIR = path.join(os.homedir(), ".ssh");
|
|
8
|
+
const SSH_CONFIG = path.join(SSH_DIR, "config");
|
|
9
|
+
|
|
10
|
+
function ensureSshHostConfig(hostAlias, hostName, identityFile) {
|
|
11
|
+
if (!fs.existsSync(SSH_DIR)) {
|
|
12
|
+
fs.mkdirSync(SSH_DIR, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let existing = "";
|
|
16
|
+
if (fs.existsSync(SSH_CONFIG)) {
|
|
17
|
+
existing = fs.readFileSync(SSH_CONFIG, "utf8");
|
|
18
|
+
const already = new RegExp(`^Host\\s+${hostAlias}\\b`, "m").test(existing);
|
|
19
|
+
if (already) {
|
|
20
|
+
logInfo(`SSH config ya tiene entrada para Host '${hostAlias}'`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const identityPath = identityFile.replace(/\\/g, "/");
|
|
26
|
+
|
|
27
|
+
const block = [
|
|
28
|
+
"",
|
|
29
|
+
`Host ${hostAlias}`,
|
|
30
|
+
` HostName ${hostName}`,
|
|
31
|
+
` User deploy`,
|
|
32
|
+
` IdentityFile ${identityPath}`,
|
|
33
|
+
` IdentitiesOnly yes`,
|
|
34
|
+
// 🎯 Para evitar el prompt interactivo de host key:
|
|
35
|
+
` StrictHostKeyChecking no`,
|
|
36
|
+
// opcional, para no llenar el known_hosts global:
|
|
37
|
+
` UserKnownHostsFile ~/.ssh/kubiy_known_hosts`,
|
|
38
|
+
""
|
|
39
|
+
].join("\n");
|
|
40
|
+
|
|
41
|
+
const newContent = existing ? `${existing.trimEnd()}\n${block}` : block.trimStart();
|
|
42
|
+
|
|
43
|
+
fs.writeFileSync(SSH_CONFIG, newContent, "utf8");
|
|
44
|
+
logSuccess(`Configurado ~/.ssh/config para Host '${hostAlias}'`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
ensureSshHostConfig,
|
|
49
|
+
SSH_DIR,
|
|
50
|
+
SSH_CONFIG,
|
|
51
|
+
};
|
package/src/ssh.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { execSync } = require("child_process");
|
|
5
|
+
const { logInfo, logSuccess, logWarn } = require("./logger");
|
|
6
|
+
|
|
7
|
+
const SSH_DIR = path.join(os.homedir(), ".kubiy");
|
|
8
|
+
const SSH_PRIV = path.join(SSH_DIR, "id_ed25519");
|
|
9
|
+
const SSH_PUB = SSH_PRIV + ".pub";
|
|
10
|
+
|
|
11
|
+
function ensureSshKey() {
|
|
12
|
+
if (!fs.existsSync(SSH_DIR)) {
|
|
13
|
+
fs.mkdirSync(SSH_DIR, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!fs.existsSync(SSH_PRIV) || !fs.existsSync(SSH_PUB)) {
|
|
17
|
+
logInfo("No existe llave SSH de Kubiy, generando una nueva...");
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
execSync(
|
|
21
|
+
`ssh-keygen -t ed25519 -f "${SSH_PRIV}" -N "" -C "kubiy-cli"`,
|
|
22
|
+
{ stdio: "inherit" }
|
|
23
|
+
);
|
|
24
|
+
logSuccess("Llave SSH generada en ~/.kubiy/");
|
|
25
|
+
} catch (e) {
|
|
26
|
+
logWarn("Error generando llave SSH:", e.message);
|
|
27
|
+
throw e;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const pub = fs.readFileSync(SSH_PUB, "utf8").trim();
|
|
32
|
+
return pub;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
SSH_DIR,
|
|
37
|
+
SSH_PRIV,
|
|
38
|
+
SSH_PUB,
|
|
39
|
+
ensureSshKey
|
|
40
|
+
};
|