komplian 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/komplian-onboard.mjs +9 -3
- package/komplian-postman.mjs +42 -1
- package/komplian-setup.mjs +477 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
2. Browser login: `gh auth login -h github.com -s repo -s read:org -w`
|
|
7
7
|
3. `npx komplian onboard --yes` (sin `@versión`: usa `latest` del registry público).
|
|
8
8
|
|
|
9
|
-
**Orden típico del equipo:**
|
|
9
|
+
**Orden típico del equipo:** **`npx komplian setup`** (todo en uno; formulario local en navegador si hace falta) **o** paso a paso: onboard → **`postman --yes`** → `mcp-tools --yes` → **`db:all:dev`** → `localhost --yes`. Ver **`ONBOARDING.md`**.
|
|
10
10
|
|
|
11
11
|
**`npm ERR! ETARGET` / “No matching version”** (también si falló **`postman`**, no solo `onboard`): es el mismo paquete npm. Usa **`npx komplian postman --yes`** **sin** `@0.4.x`; comprueba `npm config get registry` → `https://registry.npmjs.org/`; `npm cache clean --force`. Detalle: `ONBOARDING.md` en el monorepo.
|
|
12
12
|
|
package/komplian-onboard.mjs
CHANGED
|
@@ -394,10 +394,11 @@ function npmInstallEach(workspace) {
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
function usage() {
|
|
397
|
-
log(`Uso: komplian onboard | postman | mcp-tools | db:all:dev | localhost | db …`);
|
|
398
|
-
log(` ${c.bold}
|
|
397
|
+
log(`Uso: komplian setup | onboard | postman | mcp-tools | db:all:dev | localhost | db …`);
|
|
398
|
+
log(` ${c.bold}Todo en uno:${c.reset} ${c.cyan}npx komplian setup${c.reset} ${c.dim}(onboard+postman+mcp+db+localhost; formulario en navegador si hace falta)${c.reset}`);
|
|
399
|
+
log(` ${c.bold}Setup por pasos:${c.reset}`);
|
|
399
400
|
log(` 1. npx komplian onboard --yes`);
|
|
400
|
-
log(` 2. npx komplian postman
|
|
401
|
+
log(` 2. npx komplian postman --yes ${c.dim}(@komplian.com)${c.reset}`);
|
|
401
402
|
log(` 3. npx komplian mcp-tools --yes`);
|
|
402
403
|
log(` 4. npx komplian db:all:dev ${c.dim}(Postman + 3 URLs dev → ~/.komplian + .env.local)${c.reset}`);
|
|
403
404
|
log(` 5. npx komplian localhost --yes`);
|
|
@@ -486,6 +487,11 @@ function normalizeArgv(argv) {
|
|
|
486
487
|
|
|
487
488
|
async function main() {
|
|
488
489
|
const rawArgv = process.argv.slice(2);
|
|
490
|
+
if (rawArgv[0] === "setup") {
|
|
491
|
+
const { runSetup } = await import("./komplian-setup.mjs");
|
|
492
|
+
await runSetup(rawArgv.slice(1));
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
489
495
|
if (rawArgv[0] === "postman") {
|
|
490
496
|
const { runPostman } = await import("./komplian-postman.mjs");
|
|
491
497
|
await runPostman(rawArgv.slice(1));
|
package/komplian-postman.mjs
CHANGED
|
@@ -216,7 +216,7 @@ function defaultKeyPath() {
|
|
|
216
216
|
/**
|
|
217
217
|
* Orden: POSTMAN_API_KEY → ~/.komplian/postman-api-key (o KOMPLIAN_POSTMAN_KEY_FILE).
|
|
218
218
|
*/
|
|
219
|
-
function resolveApiKey() {
|
|
219
|
+
export function resolveApiKey() {
|
|
220
220
|
const env = process.env.POSTMAN_API_KEY?.trim();
|
|
221
221
|
if (env) return { key: env, source: "POSTMAN_API_KEY" };
|
|
222
222
|
const path = defaultKeyPath();
|
|
@@ -227,6 +227,21 @@ function resolveApiKey() {
|
|
|
227
227
|
return { key: "", source: null };
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
/** Guarda la clave en ~/.komplian/postman-api-key (uso: `komplian setup` vía navegador). */
|
|
231
|
+
export function savePostmanApiKeyToKomplianHome(apiKey) {
|
|
232
|
+
const k = (apiKey || "").trim();
|
|
233
|
+
if (!k) return false;
|
|
234
|
+
const keyPath = defaultKeyPath();
|
|
235
|
+
ensureSecureKomplianDir();
|
|
236
|
+
writeFileSync(keyPath, `${k}\n`, { encoding: "utf8", mode: 0o600 });
|
|
237
|
+
try {
|
|
238
|
+
chmodSync(keyPath, 0o600);
|
|
239
|
+
} catch {
|
|
240
|
+
/* Windows u otros */
|
|
241
|
+
}
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
|
|
230
245
|
function printMissingKeyHelp() {
|
|
231
246
|
log(`${c.red}✗${c.reset} No hay API key de Postman.`);
|
|
232
247
|
log(``);
|
|
@@ -656,6 +671,32 @@ async function pickWorkspaceId(apiKey, explicit) {
|
|
|
656
671
|
return w.id;
|
|
657
672
|
}
|
|
658
673
|
|
|
674
|
+
/** Validación sin `process.exit` (p. ej. formulario web local en `komplian setup`). */
|
|
675
|
+
export async function validatePostmanApiKeyForKomplian(apiKey, domain) {
|
|
676
|
+
const d = (domain || "komplian.com").trim().replace(/^@/, "");
|
|
677
|
+
const { ok, status, body } = await pmFetch(apiKey, "/me");
|
|
678
|
+
if (!ok) {
|
|
679
|
+
return {
|
|
680
|
+
ok: false,
|
|
681
|
+
error: `Postman rechazó la clave (HTTP ${status}).`,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
const email = extractEmail(body);
|
|
685
|
+
if (!email) {
|
|
686
|
+
return {
|
|
687
|
+
ok: false,
|
|
688
|
+
error: "La API de Postman no devolvió email en /me.",
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
if (!emailAllowed(email, d)) {
|
|
692
|
+
return {
|
|
693
|
+
ok: false,
|
|
694
|
+
error: `Se requiere una cuenta con email @${d}.`,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
return { ok: true };
|
|
698
|
+
}
|
|
699
|
+
|
|
659
700
|
async function verifyKomplianEmail(apiKey, domain, opts = {}) {
|
|
660
701
|
const quietOk = opts.quietSuccess === true;
|
|
661
702
|
const { ok, status, body } = await pmFetch(apiKey, "/me");
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Komplian setup — un solo comando: onboard → postman → mcp-tools → db:all:dev → localhost.
|
|
4
|
+
* Por defecto abre el navegador en localhost para datos sensibles (Postman API key, URLs Neon dev)
|
|
5
|
+
* con texto de ayuda; alternativa: --terminal-only (sin navegador).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import { createServer } from "node:http";
|
|
10
|
+
import { randomBytes } from "node:crypto";
|
|
11
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { dirname, join, resolve } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
resolveApiKey,
|
|
18
|
+
validatePostmanApiKeyForKomplian,
|
|
19
|
+
savePostmanApiKeyToKomplianHome,
|
|
20
|
+
runPostman,
|
|
21
|
+
} from "./komplian-postman.mjs";
|
|
22
|
+
import { runDbAllDev } from "./komplian-db-all-dev.mjs";
|
|
23
|
+
import { runMcpTools } from "./komplian-mcp-tools.mjs";
|
|
24
|
+
import { runLocalhost, findWorkspaceRoot } from "./komplian-localhost.mjs";
|
|
25
|
+
import { loadHomeDevDatabases } from "./komplian-db.mjs";
|
|
26
|
+
|
|
27
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const IS_WIN = process.platform === "win32";
|
|
29
|
+
|
|
30
|
+
function spawnWin(extra = {}) {
|
|
31
|
+
return IS_WIN ? { ...extra, shell: true } : extra;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const c = {
|
|
35
|
+
reset: "\x1b[0m",
|
|
36
|
+
dim: "\x1b[2m",
|
|
37
|
+
bold: "\x1b[1m",
|
|
38
|
+
cyan: "\x1b[36m",
|
|
39
|
+
green: "\x1b[32m",
|
|
40
|
+
red: "\x1b[31m",
|
|
41
|
+
yellow: "\x1b[33m",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function log(s = "") {
|
|
45
|
+
console.log(s);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const PLACEHOLDER = "komplian_localhost_placeholder";
|
|
49
|
+
|
|
50
|
+
function isValidPostgresUrl(s) {
|
|
51
|
+
const t = (s || "").trim();
|
|
52
|
+
if (!t.startsWith("postgresql://") && !t.startsWith("postgres://")) return false;
|
|
53
|
+
if (t.includes(PLACEHOLDER)) return false;
|
|
54
|
+
try {
|
|
55
|
+
const u = new URL(t);
|
|
56
|
+
return !!u.hostname;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isValidTriplet(d) {
|
|
63
|
+
return (
|
|
64
|
+
isValidPostgresUrl(d.app) &&
|
|
65
|
+
isValidPostgresUrl(d.admin) &&
|
|
66
|
+
isValidPostgresUrl(d.web)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function collectUrlsFromEnv() {
|
|
71
|
+
return {
|
|
72
|
+
app: process.env.KOMPLIAN_DEV_APP_DATABASE_URL?.trim() || "",
|
|
73
|
+
admin: process.env.KOMPLIAN_DEV_ADMIN_DATABASE_URL?.trim() || "",
|
|
74
|
+
web: process.env.KOMPLIAN_DEV_WEB_DATABASE_URL?.trim() || "",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function openBrowserSync(url) {
|
|
79
|
+
try {
|
|
80
|
+
if (process.platform === "darwin") {
|
|
81
|
+
spawnSync("open", [url], { stdio: "ignore" });
|
|
82
|
+
} else if (process.platform === "win32") {
|
|
83
|
+
spawnSync("cmd", ["/c", "start", "", url], { ...spawnWin(), stdio: "ignore" });
|
|
84
|
+
} else {
|
|
85
|
+
spawnSync("xdg-open", [url], { stdio: "ignore" });
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
log(`${c.yellow}○${c.reset} No se pudo abrir el navegador. Abre manualmente: ${c.bold}${url}${c.reset}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildSetupPageHtml(token, { needsPostmanKey, needsDbUrls, emailDomain }) {
|
|
93
|
+
const esc = (s) =>
|
|
94
|
+
String(s)
|
|
95
|
+
.replace(/&/g, "&")
|
|
96
|
+
.replace(/</g, "<")
|
|
97
|
+
.replace(/"/g, """);
|
|
98
|
+
const postmanBlock = needsPostmanKey
|
|
99
|
+
? `
|
|
100
|
+
<section class="card">
|
|
101
|
+
<h2>1. Postman API key</h2>
|
|
102
|
+
<p class="help">Crea una clave en Postman → <strong>Settings</strong> → <strong>API keys</strong> → Generate. Cuenta con email <strong>@${esc(emailDomain)}</strong>. Se guarda en <code>~/.komplian/postman-api-key</code>. Si la dejas vacía, la terminal te la pedirá en el paso Postman.</p>
|
|
103
|
+
<label for="postman_api_key">API key (opcional aquí)</label>
|
|
104
|
+
<input type="password" id="postman_api_key" name="postman_api_key" autocomplete="off" placeholder="PMAK-…" />
|
|
105
|
+
</section>`
|
|
106
|
+
: "";
|
|
107
|
+
const dbBlock = needsDbUrls
|
|
108
|
+
? `
|
|
109
|
+
<section class="card">
|
|
110
|
+
<h2>${needsPostmanKey ? "2" : "1"}. Bases de datos (solo desarrollo)</h2>
|
|
111
|
+
<p class="help">Pega las connection strings <strong>postgresql://…</strong> de Neon (rama <em>development</em> u homólogo). Tres bases: <strong>app</strong> (también usa la API), <strong>admin</strong> y <strong>web</strong> (pilot). Se escriben en <code>~/.komplian/dev-databases.json</code> y en cada <code>.env.local</code> del monorepo.</p>
|
|
112
|
+
<label for="db_app">URL APP (app + API)</label>
|
|
113
|
+
<textarea id="db_app" name="db_app" rows="2" placeholder="postgresql://…"></textarea>
|
|
114
|
+
<label for="db_admin">URL ADMIN</label>
|
|
115
|
+
<textarea id="db_admin" name="db_admin" rows="2" placeholder="postgresql://…"></textarea>
|
|
116
|
+
<label for="db_web">URL WEB (pilot)</label>
|
|
117
|
+
<textarea id="db_web" name="db_web" rows="2" placeholder="postgresql://…"></textarea>
|
|
118
|
+
</section>`
|
|
119
|
+
: "";
|
|
120
|
+
return `<!DOCTYPE html>
|
|
121
|
+
<html lang="es">
|
|
122
|
+
<head>
|
|
123
|
+
<meta charset="utf-8" />
|
|
124
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
125
|
+
<title>Komplian setup</title>
|
|
126
|
+
<style>
|
|
127
|
+
:root { font-family: system-ui, sans-serif; background: #0a0a0a; color: #e5e5e5; }
|
|
128
|
+
body { max-width: 640px; margin: 2rem auto; padding: 0 1rem; }
|
|
129
|
+
h1 { font-size: 1.25rem; font-weight: 600; }
|
|
130
|
+
.card { background: #171717; border: 1px solid #262626; border-radius: 10px; padding: 1.25rem; margin-bottom: 1.25rem; }
|
|
131
|
+
.help { color: #a3a3a3; font-size: 0.9rem; line-height: 1.5; margin: 0 0 1rem; }
|
|
132
|
+
label { display: block; font-size: 0.8rem; color: #737373; margin: 0.75rem 0 0.35rem; }
|
|
133
|
+
input, textarea { width: 100%; box-sizing: border-box; padding: 0.6rem 0.75rem; border-radius: 8px; border: 1px solid #404040; background: #0a0a0a; color: #fafafa; font-family: ui-monospace, monospace; font-size: 0.85rem; }
|
|
134
|
+
button { margin-top: 1.25rem; width: 100%; padding: 0.85rem; border: none; border-radius: 8px; background: #fafafa; color: #0a0a0a; font-weight: 600; cursor: pointer; font-size: 1rem; }
|
|
135
|
+
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
136
|
+
.err { color: #f87171; font-size: 0.9rem; margin-top: 0.75rem; white-space: pre-wrap; }
|
|
137
|
+
.ok { color: #4ade80; margin-top: 1rem; }
|
|
138
|
+
code { font-size: 0.8em; background: #262626; padding: 0.1em 0.35em; border-radius: 4px; }
|
|
139
|
+
</style>
|
|
140
|
+
</head>
|
|
141
|
+
<body>
|
|
142
|
+
<h1>Komplian — asistente de setup</h1>
|
|
143
|
+
<p class="help">Esta ventana es local (solo tu ordenador). Rellena lo que pida el asistente y pulsa <strong>Continuar</strong>. Puedes cerrar la pestaña después del mensaje de éxito.</p>
|
|
144
|
+
${postmanBlock}
|
|
145
|
+
${dbBlock}
|
|
146
|
+
<div id="err" class="err" style="display:none"></div>
|
|
147
|
+
<div id="ok" class="ok" style="display:none"></div>
|
|
148
|
+
<button type="button" id="go">Continuar</button>
|
|
149
|
+
<script>
|
|
150
|
+
const TOKEN = ${JSON.stringify(token)};
|
|
151
|
+
const needsPostman = ${JSON.stringify(needsPostmanKey)};
|
|
152
|
+
const needsDb = ${JSON.stringify(needsDbUrls)};
|
|
153
|
+
document.getElementById("go").onclick = async () => {
|
|
154
|
+
const err = document.getElementById("err");
|
|
155
|
+
const ok = document.getElementById("ok");
|
|
156
|
+
err.style.display = "none";
|
|
157
|
+
ok.style.display = "none";
|
|
158
|
+
const body = { token: TOKEN };
|
|
159
|
+
if (needsPostman) body.postman_api_key = (document.getElementById("postman_api_key") || {}).value || "";
|
|
160
|
+
if (needsDb) {
|
|
161
|
+
body.db_app = (document.getElementById("db_app") || {}).value || "";
|
|
162
|
+
body.db_admin = (document.getElementById("db_admin") || {}).value || "";
|
|
163
|
+
body.db_web = (document.getElementById("db_web") || {}).value || "";
|
|
164
|
+
}
|
|
165
|
+
const btn = document.getElementById("go");
|
|
166
|
+
btn.disabled = true;
|
|
167
|
+
try {
|
|
168
|
+
const r = await fetch("/submit", {
|
|
169
|
+
method: "POST",
|
|
170
|
+
headers: { "Content-Type": "application/json" },
|
|
171
|
+
body: JSON.stringify(body),
|
|
172
|
+
});
|
|
173
|
+
const j = await r.json().catch(() => ({}));
|
|
174
|
+
if (!r.ok || !j.ok) {
|
|
175
|
+
err.textContent = j.error || "Error al validar. Revisa los datos.";
|
|
176
|
+
err.style.display = "block";
|
|
177
|
+
btn.disabled = false;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
ok.textContent = "Listo. Vuelve a la terminal; puedes cerrar esta pestaña.";
|
|
181
|
+
ok.style.display = "block";
|
|
182
|
+
} catch (e) {
|
|
183
|
+
err.textContent = "No se pudo enviar. ¿Sigues en la misma red local?";
|
|
184
|
+
err.style.display = "block";
|
|
185
|
+
btn.disabled = false;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
</script>
|
|
189
|
+
</body>
|
|
190
|
+
</html>`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Servidor solo en 127.0.0.1. Devuelve { postmanKey?, db } según lo pedido.
|
|
195
|
+
*/
|
|
196
|
+
function runSetupBrowserForm(opts) {
|
|
197
|
+
const {
|
|
198
|
+
needsPostmanKey,
|
|
199
|
+
needsDbUrls,
|
|
200
|
+
emailDomain,
|
|
201
|
+
timeoutMs = 600_000,
|
|
202
|
+
} = opts;
|
|
203
|
+
|
|
204
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
205
|
+
const token = randomBytes(24).toString("hex");
|
|
206
|
+
const server = createServer(async (req, res) => {
|
|
207
|
+
const url = new URL(req.url || "/", `http://127.0.0.1`);
|
|
208
|
+
|
|
209
|
+
if (req.method === "GET" && url.pathname === "/") {
|
|
210
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
211
|
+
res.end(
|
|
212
|
+
buildSetupPageHtml(token, {
|
|
213
|
+
needsPostmanKey,
|
|
214
|
+
needsDbUrls,
|
|
215
|
+
emailDomain,
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (req.method === "POST" && url.pathname === "/submit") {
|
|
222
|
+
let raw = "";
|
|
223
|
+
for await (const ch of req) raw += ch;
|
|
224
|
+
let data;
|
|
225
|
+
try {
|
|
226
|
+
data = JSON.parse(raw || "{}");
|
|
227
|
+
} catch {
|
|
228
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
229
|
+
res.end(JSON.stringify({ ok: false, error: "JSON inválido." }));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (data.token !== token) {
|
|
233
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
234
|
+
res.end(JSON.stringify({ ok: false, error: "Token inválido." }));
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const out = { postmanKey: "", db: null };
|
|
239
|
+
|
|
240
|
+
if (needsPostmanKey) {
|
|
241
|
+
const pk = String(data.postman_api_key || "").trim();
|
|
242
|
+
if (pk) {
|
|
243
|
+
const v = await validatePostmanApiKeyForKomplian(pk, emailDomain);
|
|
244
|
+
if (!v.ok) {
|
|
245
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
246
|
+
res.end(JSON.stringify({ ok: false, error: v.error || "Postman inválido." }));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
out.postmanKey = pk;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (needsDbUrls) {
|
|
254
|
+
const triplet = {
|
|
255
|
+
app: String(data.db_app || "").trim(),
|
|
256
|
+
admin: String(data.db_admin || "").trim(),
|
|
257
|
+
web: String(data.db_web || "").trim(),
|
|
258
|
+
};
|
|
259
|
+
if (!isValidTriplet(triplet)) {
|
|
260
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
261
|
+
res.end(
|
|
262
|
+
JSON.stringify({
|
|
263
|
+
ok: false,
|
|
264
|
+
error:
|
|
265
|
+
"Las tres URLs deben ser postgresql:// o postgres:// válidas (sin placeholder).",
|
|
266
|
+
})
|
|
267
|
+
);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
out.db = triplet;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
274
|
+
res.end(JSON.stringify({ ok: true }));
|
|
275
|
+
|
|
276
|
+
server.close(() => {
|
|
277
|
+
clearTimeout(timer);
|
|
278
|
+
resolvePromise(out);
|
|
279
|
+
});
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
res.writeHead(404);
|
|
284
|
+
res.end();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const timer = setTimeout(() => {
|
|
288
|
+
server.close();
|
|
289
|
+
rejectPromise(new Error("Tiempo agotado esperando el formulario en el navegador."));
|
|
290
|
+
}, timeoutMs);
|
|
291
|
+
|
|
292
|
+
server.listen(0, "127.0.0.1", () => {
|
|
293
|
+
const addr = server.address();
|
|
294
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
295
|
+
const openUrl = `http://127.0.0.1:${port}/`;
|
|
296
|
+
log("");
|
|
297
|
+
log(
|
|
298
|
+
`${c.cyan}━━ Navegador (formulario local) ━━${c.reset} ${c.bold}${openUrl}${c.reset}`
|
|
299
|
+
);
|
|
300
|
+
log(
|
|
301
|
+
`${c.dim}Solo escucha en tu máquina (127.0.0.1). Cierra la pestaña tras el mensaje de éxito.${c.reset}`
|
|
302
|
+
);
|
|
303
|
+
openBrowserSync(openUrl);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
server.on("error", (e) => {
|
|
307
|
+
clearTimeout(timer);
|
|
308
|
+
rejectPromise(e);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function parseArgs(argv) {
|
|
314
|
+
const out = {
|
|
315
|
+
terminalOnly: false,
|
|
316
|
+
workspace: "",
|
|
317
|
+
help: false,
|
|
318
|
+
team: "",
|
|
319
|
+
ssh: false,
|
|
320
|
+
allRepos: false,
|
|
321
|
+
noInstall: false,
|
|
322
|
+
};
|
|
323
|
+
for (let i = 0; i < argv.length; i++) {
|
|
324
|
+
const a = argv[i];
|
|
325
|
+
if (a === "--terminal-only" || a === "--no-browser")
|
|
326
|
+
out.terminalOnly = true;
|
|
327
|
+
else if (a === "-w" || a === "--workspace") out.workspace = argv[++i] || "";
|
|
328
|
+
else if (a === "-t" || a === "--team") out.team = argv[++i] || "";
|
|
329
|
+
else if (a === "--ssh") out.ssh = true;
|
|
330
|
+
else if (a === "--all-repos") out.allRepos = true;
|
|
331
|
+
else if (a === "--no-install") out.noInstall = true;
|
|
332
|
+
else if (a === "-h" || a === "--help") out.help = true;
|
|
333
|
+
else if (a.startsWith("-")) {
|
|
334
|
+
log(`${c.red}✗${c.reset} Opción desconocida: ${a}`);
|
|
335
|
+
process.exit(1);
|
|
336
|
+
} else if (!out.workspace) out.workspace = a;
|
|
337
|
+
}
|
|
338
|
+
return out;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function usage() {
|
|
342
|
+
log(`Uso: npx komplian setup [opciones] [carpeta-workspace]`);
|
|
343
|
+
log(``);
|
|
344
|
+
log(
|
|
345
|
+
` Orden: ${c.bold}onboard${c.reset} → ${c.bold}postman${c.reset} → ${c.bold}mcp-tools${c.reset} → ${c.bold}db:all:dev${c.reset} → ${c.bold}localhost${c.reset}`
|
|
346
|
+
);
|
|
347
|
+
log(
|
|
348
|
+
` Por defecto abre el ${c.bold}navegador${c.reset} para Postman API key (si falta) y las 3 URLs Neon dev (si faltan).`
|
|
349
|
+
);
|
|
350
|
+
log(``);
|
|
351
|
+
log(` --terminal-only Sin formulario web; postman/db usan terminal o env`);
|
|
352
|
+
log(` -w, --workspace Raíz del monorepo (destino del clone)`);
|
|
353
|
+
log(` -t, --team Equipo (komplian-team-repos.json)`);
|
|
354
|
+
log(` --all-repos Clonar todos los repos del JSON`);
|
|
355
|
+
log(` --ssh Clonar por SSH`);
|
|
356
|
+
log(` --no-install No npm install tras clone`);
|
|
357
|
+
log(` -h, --help`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function runOnboardChild(args) {
|
|
361
|
+
const script = join(__dirname, "komplian-onboard.mjs");
|
|
362
|
+
const argv = ["onboard", ...args];
|
|
363
|
+
const r = spawnSync(process.execPath, [script, ...argv], {
|
|
364
|
+
stdio: "inherit",
|
|
365
|
+
windowsHide: true,
|
|
366
|
+
});
|
|
367
|
+
return r.status === 0;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export async function runSetup(argv) {
|
|
371
|
+
const opts = parseArgs(argv);
|
|
372
|
+
if (opts.help) {
|
|
373
|
+
usage();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const nodeMajor = Number(process.versions.node.split(".")[0], 10);
|
|
378
|
+
if (nodeMajor < 18) {
|
|
379
|
+
log(`${c.red}✗${c.reset} Hace falta Node 18+.`);
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
let workspaceArg = (opts.workspace || "").trim();
|
|
384
|
+
if (!workspaceArg) workspaceArg = process.cwd();
|
|
385
|
+
const workspaceAbs = resolve(
|
|
386
|
+
workspaceArg.replace(/^~(?=$|[/\\])/, homedir())
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
mkdirSync(workspaceAbs, { recursive: true });
|
|
390
|
+
|
|
391
|
+
log(`${c.cyan}${c.bold}━━ Komplian setup ━━${c.reset}`);
|
|
392
|
+
log(`${c.dim}Workspace:${c.reset} ${workspaceAbs}`);
|
|
393
|
+
log("");
|
|
394
|
+
|
|
395
|
+
const onboardArgs = ["--yes"];
|
|
396
|
+
if (opts.team) onboardArgs.push("-t", opts.team);
|
|
397
|
+
if (opts.allRepos) onboardArgs.push("--all-repos");
|
|
398
|
+
if (opts.ssh) onboardArgs.push("--ssh");
|
|
399
|
+
if (opts.noInstall) onboardArgs.push("--no-install");
|
|
400
|
+
onboardArgs.push(workspaceAbs);
|
|
401
|
+
|
|
402
|
+
log(`${c.cyan}━━ 1/5 Onboard ━━${c.reset}`);
|
|
403
|
+
if (!runOnboardChild(onboardArgs)) {
|
|
404
|
+
log(`${c.red}✗${c.reset} Onboard falló. Revisa GitHub CLI (gh) y permisos.`);
|
|
405
|
+
process.exit(1);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
let monorepoRoot = workspaceAbs;
|
|
409
|
+
if (!existsSync(join(monorepoRoot, "api", "package.json"))) {
|
|
410
|
+
monorepoRoot = findWorkspaceRoot(workspaceAbs);
|
|
411
|
+
}
|
|
412
|
+
if (!existsSync(join(monorepoRoot, "api", "package.json"))) {
|
|
413
|
+
log(
|
|
414
|
+
`${c.red}✗${c.reset} No se encontró monorepo con api/package.json bajo ${workspaceAbs}.`
|
|
415
|
+
);
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
process.chdir(monorepoRoot);
|
|
420
|
+
|
|
421
|
+
const emailDomain = (process.env.KOMPLIAN_EMAIL_DOMAIN || "komplian.com").trim();
|
|
422
|
+
const useBrowser =
|
|
423
|
+
!opts.terminalOnly && process.env.KOMPLIAN_SETUP_NO_BROWSER !== "1";
|
|
424
|
+
|
|
425
|
+
const envTriplet = collectUrlsFromEnv();
|
|
426
|
+
const envDbOk = isValidTriplet(envTriplet);
|
|
427
|
+
const saved = loadHomeDevDatabases();
|
|
428
|
+
const savedDbOk = saved && isValidTriplet(saved);
|
|
429
|
+
const { key: existingPmKey } = resolveApiKey();
|
|
430
|
+
const needsPostmanKey = !existingPmKey;
|
|
431
|
+
const needsDbUrls = !envDbOk && !savedDbOk;
|
|
432
|
+
|
|
433
|
+
if (useBrowser && (needsPostmanKey || needsDbUrls)) {
|
|
434
|
+
log(`${c.cyan}━━ Formulario en el navegador ━━${c.reset}`);
|
|
435
|
+
try {
|
|
436
|
+
const form = await runSetupBrowserForm({
|
|
437
|
+
needsPostmanKey,
|
|
438
|
+
needsDbUrls,
|
|
439
|
+
emailDomain,
|
|
440
|
+
});
|
|
441
|
+
if (form.postmanKey) {
|
|
442
|
+
savePostmanApiKeyToKomplianHome(form.postmanKey);
|
|
443
|
+
log(`${c.green}✓${c.reset} Postman API key guardada en ~/.komplian/`);
|
|
444
|
+
}
|
|
445
|
+
if (form.db) {
|
|
446
|
+
process.env.KOMPLIAN_DEV_APP_DATABASE_URL = form.db.app;
|
|
447
|
+
process.env.KOMPLIAN_DEV_ADMIN_DATABASE_URL = form.db.admin;
|
|
448
|
+
process.env.KOMPLIAN_DEV_WEB_DATABASE_URL = form.db.web;
|
|
449
|
+
log(`${c.green}✓${c.reset} URLs de desarrollo recibidas desde el formulario.`);
|
|
450
|
+
}
|
|
451
|
+
} catch (e) {
|
|
452
|
+
log(
|
|
453
|
+
`${c.red}✗${c.reset} Formulario web: ${e?.message || e}. Prueba ${c.bold}--terminal-only${c.reset} o define variables de entorno.`
|
|
454
|
+
);
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
log(`${c.cyan}━━ 2/5 Postman ━━${c.reset}`);
|
|
460
|
+
await runPostman(["--yes"]);
|
|
461
|
+
|
|
462
|
+
log(`${c.cyan}━━ 3/5 MCP tools ━━${c.reset}`);
|
|
463
|
+
await runMcpTools(["--yes"]);
|
|
464
|
+
|
|
465
|
+
log(`${c.cyan}━━ 4/5 Bases de datos (development) ━━${c.reset}`);
|
|
466
|
+
const dbArgs = ["--yes", "-w", monorepoRoot];
|
|
467
|
+
await runDbAllDev(dbArgs);
|
|
468
|
+
|
|
469
|
+
log(`${c.cyan}━━ 5/5 Localhost ━━${c.reset}`);
|
|
470
|
+
await runLocalhost(["--yes"]);
|
|
471
|
+
|
|
472
|
+
log("");
|
|
473
|
+
log(`${c.green}✓${c.reset} ${c.bold}Setup completado.${c.reset}`);
|
|
474
|
+
log(
|
|
475
|
+
`${c.dim}Monorepo:${c.reset} ${monorepoRoot} ${c.dim}· Cursor: File → Open Folder${c.reset}`
|
|
476
|
+
);
|
|
477
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "komplian",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Komplian CLI: onboard, Postman, localhost, mcp-tools, db (psql). Node 18+.
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Komplian CLI: setup (todo en uno), onboard, Postman, localhost, mcp-tools, db (psql). Node 18+.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=18"
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"komplian-onboard.mjs",
|
|
14
|
+
"komplian-setup.mjs",
|
|
14
15
|
"komplian-postman.mjs",
|
|
15
16
|
"komplian-localhost.mjs",
|
|
16
17
|
"komplian-mcp-tools.mjs",
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
"access": "public"
|
|
24
25
|
},
|
|
25
26
|
"scripts": {
|
|
26
|
-
"prepublishOnly": "node --check komplian-onboard.mjs && node --check komplian-postman.mjs && node --check komplian-localhost.mjs && node --check komplian-mcp-tools.mjs && node --check komplian-db.mjs && node --check komplian-db-all-dev.mjs"
|
|
27
|
+
"prepublishOnly": "node --check komplian-onboard.mjs && node --check komplian-setup.mjs && node --check komplian-postman.mjs && node --check komplian-localhost.mjs && node --check komplian-mcp-tools.mjs && node --check komplian-db.mjs && node --check komplian-db-all-dev.mjs"
|
|
27
28
|
},
|
|
28
29
|
"keywords": [
|
|
29
30
|
"komplian",
|