quick-outerbase 0.3.6 → 0.4.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.
Files changed (2) hide show
  1. package/launcher.mjs +286 -257
  2. package/package.json +34 -33
package/launcher.mjs CHANGED
@@ -1,257 +1,286 @@
1
- #!/usr/bin/env node
2
- // Launcher fino de quick-outerbase (publicado en npm como `quick-outerbase`).
3
- // NO buildea ni trae dependencias pesadas: detecta tu plataforma, baja UNA vez
4
- // el bundle `standalone` precompilado desde GitHub Releases, lo cachea y corre
5
- // `node server.js`. Primer run: ~descarga + arranque (segundos). Siguientes:
6
- // instantáneo (cacheado por versión+plataforma).
7
- //
8
- // Uso: npx quick-outerbase --url "postgresql://user:pass@host:5432/db?schema=public"
9
- // (o posicional, o env DATABASE_URL). Flags: --port, --no-open.
10
- //
11
- // Override para testing/offline: QUICK_OUTERBASE_BUNDLE=/ruta/a/bundle.tar.gz
12
- import { spawn, spawnSync } from "node:child_process";
13
- import { copyFileSync, createWriteStream, existsSync, mkdirSync, rmSync } from "node:fs";
14
- import { Readable } from "node:stream";
15
- import { pipeline } from "node:stream/promises";
16
- import http from "node:http";
17
- import os from "node:os";
18
- import path from "node:path";
19
- import process from "node:process";
20
- import { fileURLToPath } from "node:url";
21
- import { createRequire } from "node:module";
22
-
23
- const require = createRequire(import.meta.url);
24
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
25
- const pkg = require("./package.json");
26
- const VERSION = pkg.version;
27
- const REPO = "joajo13/quick-outerbase";
28
- const isWin = process.platform === "win32";
29
-
30
- function fail(msg) {
31
- console.error("\x1b[31m" + msg + "\x1b[0m");
32
- process.exit(1);
33
- }
34
-
35
- // --- Guard de versión de Node (el server.js standalone necesita Node 20.9+) ---
36
- {
37
- const [maj, min] = process.versions.node.split(".").map(Number);
38
- if (maj < 20 || (maj === 20 && min < 9)) {
39
- fail(`Necesitás Node 20.9+ (tenés ${process.versions.node}). Actualizá Node y reintentá.`);
40
- }
41
- }
42
-
43
- // --- Parseo de argumentos (mismo contrato que el comando original) ---
44
- function getArg(name) {
45
- const i = process.argv.indexOf(name);
46
- return i >= 0 ? process.argv[i + 1] : undefined;
47
- }
48
- function positionalUrl() {
49
- return process.argv
50
- .slice(2)
51
- .find((a) => /^(postgres|postgresql|mysql|mariadb|sqlite|file|libsql):/i.test(a));
52
- }
53
- const url = getArg("--url") || positionalUrl() || process.env.DATABASE_URL;
54
- const port = getArg("--port") || process.env.PORT || "3008";
55
- const noOpen = process.argv.includes("--no-open");
56
-
57
- if (!url) {
58
- fail(
59
- "Falta DATABASE_URL. Pasalo con --url <connection-string> o por la env DATABASE_URL.\n" +
60
- 'Ej: npx quick-outerbase --url "postgresql://user:pass@localhost:5432/db?schema=public"'
61
- );
62
- }
63
-
64
- // --- Validación del motor por el scheme ---
65
- const scheme = (url.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/) || [])[1]?.toLowerCase();
66
- const SUPPORTED = new Set(["postgres", "postgresql", "mysql", "mariadb", "sqlite", "file", "libsql"]);
67
- if (!scheme || !SUPPORTED.has(scheme)) {
68
- fail(
69
- `Scheme no reconocido: "${scheme || "(ninguno)"}". ` +
70
- "Motores soportados: postgres://, postgresql://, mysql://, sqlite:/file:, libsql://"
71
- );
72
- }
73
-
74
- // --- SQLite: resolver path relativo contra el cwd del USUARIO (no el cache) y
75
- // pasar a libsql una URL file: absoluta (anda en Windows). ---
76
- const userCwd = process.cwd();
77
- function normalizeDbUrl(raw) {
78
- const m = raw.match(/^(sqlite|file):(.*)$/i);
79
- if (!m) return raw;
80
- const p = m[2].replace(/^\/\//, "");
81
- if (!p) return raw;
82
- const isAbs = path.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p);
83
- const abs = isAbs ? p : path.resolve(userCwd, p);
84
- return "file:" + abs.split("\\").join("/");
85
- }
86
- const runUrl = normalizeDbUrl(url);
87
- const redacted = url.replace(/\/\/([^:/@]+):([^@]+)@/, "//$1:***@");
88
- console.log(`▶ quick-outerbase v${VERSION} ${scheme} (${redacted})`);
89
-
90
- // --- Plataforma → nombre del asset del release ---
91
- const ARCH = process.arch; // x64 | arm64
92
- const PLAT = process.platform; // win32 | linux | darwin
93
- // Bundles precompilados: solo Linux y Windows. macOS quedó fuera a propósito
94
- // (los runners de macOS alargan mucho el CI). En Mac, corré desde el código.
95
- const SUPPORTED_TARGETS = new Set(["win32-x64", "linux-x64"]);
96
- const target = `${PLAT}-${ARCH}`;
97
- if (!SUPPORTED_TARGETS.has(target)) {
98
- fail(
99
- `No hay bundle precompilado para tu plataforma (${target}).\n` +
100
- "Soportadas: win32-x64, linux-x64.\n" +
101
- "Alternativa: corré desde el código con `npx github:" + REPO + "`."
102
- );
103
- }
104
- const assetName = `quick-outerbase-${target}.tar.gz`;
105
- const assetUrl = `https://github.com/${REPO}/releases/download/v${VERSION}/${assetName}`;
106
-
107
- // --- Cache por versión+plataforma ---
108
- const cacheRoot =
109
- process.env.QUICK_OUTERBASE_CACHE ||
110
- path.join(os.homedir() || os.tmpdir(), ".cache", "quick-outerbase");
111
- const bundleDir = path.join(cacheRoot, `${VERSION}-${target}`);
112
- const serverJs = path.join(bundleDir, "server.js");
113
-
114
- async function ensureBundle() {
115
- if (existsSync(serverJs)) return; // ya cacheado
116
- mkdirSync(bundleDir, { recursive: true });
117
- // El .tgz va ADENTRO del dir destino y extraemos con cwd + basename: así
118
- // tar nunca recibe una ruta con ':' (GNU tar la tomaría como host remoto).
119
- const innerTgz = path.join(bundleDir, "_bundle.tar.gz");
120
-
121
- const localOverride = process.env.QUICK_OUTERBASE_BUNDLE;
122
- if (localOverride) {
123
- if (!existsSync(localOverride)) fail(`QUICK_OUTERBASE_BUNDLE no existe: ${localOverride}`);
124
- copyFileSync(localOverride, innerTgz);
125
- } else {
126
- await download(assetUrl, innerTgz);
127
- }
128
- extractInDir(bundleDir, "_bundle.tar.gz");
129
- try {
130
- rmSync(innerTgz, { force: true });
131
- } catch {
132
- /* noop */
133
- }
134
- }
135
-
136
- async function download(fromUrl, toFile) {
137
- let res;
138
- try {
139
- res = await fetch(fromUrl, { redirect: "follow" });
140
- } catch (e) {
141
- fail(`No pude descargar el runtime (${fromUrl}): ${e.message}`);
142
- }
143
- if (!res.ok) {
144
- fail(
145
- `No pude descargar el runtime (HTTP ${res.status}) de:\n ${fromUrl}\n` +
146
- `¿Existe el release v${VERSION} con el asset ${assetName}?`
147
- );
148
- }
149
- await pipeline(Readable.fromWeb(res.body), createWriteStream(toFile));
150
- }
151
-
152
- function extractInDir(dir, fname) {
153
- // tar disponible en Windows 10+ (tar.exe/bsdtar), macOS y Linux. Corremos con
154
- // cwd=dir y solo el basename → sin rutas con ':' que rompan GNU tar.
155
- const r = spawnSync("tar", ["-xzf", fname], { cwd: dir, stdio: "inherit" });
156
- if (r.error || r.status !== 0) {
157
- fail(
158
- "Falló la extracción con `tar`. Asegurate de tener `tar` en el PATH " +
159
- "(Windows 10+ lo trae como tar.exe)."
160
- );
161
- }
162
- }
163
-
164
- // --- Arranque del server standalone + teardown limpio ---
165
- async function main() {
166
- await ensureBundle();
167
- if (!existsSync(serverJs)) fail(`No encontré server.js en ${bundleDir} tras extraer.`);
168
-
169
- const child = spawn(process.execPath, [serverJs], {
170
- cwd: bundleDir,
171
- stdio: "inherit",
172
- env: {
173
- ...process.env,
174
- DATABASE_URL: runUrl,
175
- PORT: String(port),
176
- HOSTNAME: process.env.HOSTNAME || "127.0.0.1",
177
- FORK_LOCAL: "1",
178
- },
179
- });
180
-
181
- let tearingDown = false;
182
- function teardown(code = 0) {
183
- if (tearingDown) return;
184
- tearingDown = true;
185
- console.log("\n• Cerrando…");
186
- try {
187
- if (isWin) spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
188
- else process.kill(child.pid, "SIGTERM");
189
- } catch {
190
- /* ya muerto */
191
- }
192
- if (isWin) {
193
- try {
194
- const out = spawnSync("netstat", ["-ano"], { encoding: "utf8" }).stdout || "";
195
- const pids = new Set();
196
- for (const line of out.split(/\r?\n/)) {
197
- if (line.includes(":" + port + " ") && /LISTENING/i.test(line)) {
198
- const pid = line.trim().split(/\s+/).pop();
199
- if (pid && /^\d+$/.test(pid)) pids.add(pid);
200
- }
201
- }
202
- for (const pid of pids) spawnSync("taskkill", ["/PID", pid, "/T", "/F"], { stdio: "ignore" });
203
- } catch {
204
- /* best-effort */
205
- }
206
- }
207
- console.log("• Listo. Puerto liberado, sin procesos zombie.");
208
- process.exit(code);
209
- }
210
- process.on("SIGINT", () => teardown(0));
211
- process.on("SIGTERM", () => teardown(0));
212
- child.on("exit", (code) => {
213
- if (!tearingDown) teardown(code ?? 0);
214
- });
215
-
216
- if (!noOpen) {
217
- const targetUrl = `http://localhost:${port}/env`;
218
- const started = Date.now();
219
- // Puede haber varios requests del poll en vuelo a la vez (el server tarda más
220
- // que el intervalo en responder el primero). Sin este guard, dos respuestas OK
221
- // abrían el navegador dos veces. `opened` asegura una sola apertura.
222
- let opened = false;
223
- const poll = setInterval(() => {
224
- if (tearingDown) return clearInterval(poll);
225
- const req = http.get(
226
- { host: "localhost", port: Number(port), path: "/env", timeout: 2000 },
227
- (res) => {
228
- res.destroy();
229
- clearInterval(poll);
230
- if (opened) return;
231
- opened = true;
232
- console.log(`✔ Listo en ${targetUrl}`);
233
- openBrowser(targetUrl);
234
- }
235
- );
236
- req.on("error", () => {
237
- if (Date.now() - started > 60000) {
238
- clearInterval(poll);
239
- console.warn("No confirmé el arranque; abrí manualmente " + targetUrl);
240
- }
241
- });
242
- req.on("timeout", () => req.destroy());
243
- }, 800);
244
- }
245
- }
246
-
247
- function openBrowser(u) {
248
- try {
249
- if (isWin) spawn("cmd", ["/c", "start", "", u], { stdio: "ignore", detached: true });
250
- else if (process.platform === "darwin") spawn("open", [u], { stdio: "ignore", detached: true });
251
- else spawn("xdg-open", [u], { stdio: "ignore", detached: true });
252
- } catch {
253
- /* sin browser, no es fatal */
254
- }
255
- }
256
-
257
- main().catch((e) => fail("Error: " + (e?.message || e)));
1
+ #!/usr/bin/env node
2
+ // Launcher fino de quick-outerbase (publicado en npm como `quick-outerbase`).
3
+ // NO buildea ni trae dependencias pesadas: detecta tu plataforma, baja UNA vez
4
+ // el bundle `standalone` precompilado desde GitHub Releases, lo cachea y corre
5
+ // `node server.js`. Primer run: ~descarga + arranque (segundos). Siguientes:
6
+ // instantáneo (cacheado por versión+plataforma).
7
+ //
8
+ // Uso: npx quick-outerbase --url "postgresql://user:pass@host:5432/db?schema=public"
9
+ // (o posicional, o env DATABASE_URL). Flags: --port, --no-open.
10
+ //
11
+ // Override para testing/offline: QUICK_OUTERBASE_BUNDLE=/ruta/a/bundle.tar.gz
12
+ import { spawn, spawnSync } from "node:child_process";
13
+ import { copyFileSync, createWriteStream, existsSync, mkdirSync, rmSync } from "node:fs";
14
+ import { Readable } from "node:stream";
15
+ import { pipeline } from "node:stream/promises";
16
+ import http from "node:http";
17
+ import os from "node:os";
18
+ import path from "node:path";
19
+ import process from "node:process";
20
+ import { fileURLToPath } from "node:url";
21
+ import { createRequire } from "node:module";
22
+
23
+ const require = createRequire(import.meta.url);
24
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
25
+ const pkg = require("./package.json");
26
+ const VERSION = pkg.version;
27
+ const REPO = "joajo13/quick-outerbase";
28
+ const isWin = process.platform === "win32";
29
+
30
+ function fail(msg) {
31
+ console.error("\x1b[31m" + msg + "\x1b[0m");
32
+ process.exit(1);
33
+ }
34
+
35
+ // --- Guard de versión de Node (el server.js standalone necesita Node 20.9+) ---
36
+ {
37
+ const [maj, min] = process.versions.node.split(".").map(Number);
38
+ if (maj < 20 || (maj === 20 && min < 9)) {
39
+ fail(`Necesitás Node 20.9+ (tenés ${process.versions.node}). Actualizá Node y reintentá.`);
40
+ }
41
+ }
42
+
43
+ // --- Parseo de argumentos (mismo contrato que el comando original) ---
44
+ function getArg(name) {
45
+ const i = process.argv.indexOf(name);
46
+ return i >= 0 ? process.argv[i + 1] : undefined;
47
+ }
48
+ function positionalUrl() {
49
+ return process.argv
50
+ .slice(2)
51
+ .find((a) =>
52
+ /^(postgres|postgresql|mysql|mariadb|sqlite|file|libsql|dynamodb):/i.test(a)
53
+ );
54
+ }
55
+ const url = getArg("--url") || positionalUrl() || process.env.DATABASE_URL;
56
+ const port = getArg("--port") || process.env.PORT || "3008";
57
+ const noOpen = process.argv.includes("--no-open");
58
+
59
+ if (!url) {
60
+ fail(
61
+ "Falta DATABASE_URL. Pasalo con --url <connection-string> o por la env DATABASE_URL.\n" +
62
+ 'Ej: npx quick-outerbase --url "postgresql://user:pass@localhost:5432/db?schema=public"'
63
+ );
64
+ }
65
+
66
+ // --- Validación del motor por el scheme ---
67
+ const scheme = (url.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):/) || [])[1]?.toLowerCase();
68
+ const SUPPORTED = new Set([
69
+ "postgres",
70
+ "postgresql",
71
+ "mysql",
72
+ "mariadb",
73
+ "sqlite",
74
+ "file",
75
+ "libsql",
76
+ "dynamodb",
77
+ ]);
78
+ if (!scheme || !SUPPORTED.has(scheme)) {
79
+ fail(
80
+ `Scheme no reconocido: "${scheme || "(ninguno)"}". ` +
81
+ "Motores soportados: postgres://, postgresql://, mysql://, sqlite:/file:, libsql://, dynamodb://<region>"
82
+ );
83
+ }
84
+
85
+ // DynamoDB: la URL lleva SOLO región (+endpoint opcional). Las credenciales NO van
86
+ // en la URL: las resuelve el server standalone desde la cadena estándar de AWS
87
+ // (env AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN, ~/.aws/credentials o IAM role),
88
+ // heredadas vía process.env al spawnear server.js más abajo.
89
+ if (scheme === "dynamodb") {
90
+ const hasEnvCreds =
91
+ process.env.AWS_ACCESS_KEY_ID ||
92
+ process.env.AWS_PROFILE ||
93
+ process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI ||
94
+ process.env.AWS_WEB_IDENTITY_TOKEN_FILE;
95
+ if (!hasEnvCreds) {
96
+ console.warn(
97
+ "\x1b[33m⚠ DynamoDB: no detecté credenciales AWS en el entorno. Si no usás un perfil " +
98
+ "(~/.aws/credentials) ni un IAM role, seteá AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY antes de correr.\x1b[0m"
99
+ );
100
+ }
101
+ }
102
+
103
+ // --- SQLite: resolver path relativo contra el cwd del USUARIO (no el cache) y
104
+ // pasar a libsql una URL file: absoluta (anda en Windows). ---
105
+ const userCwd = process.cwd();
106
+ function normalizeDbUrl(raw) {
107
+ const m = raw.match(/^(sqlite|file):(.*)$/i);
108
+ if (!m) return raw;
109
+ const p = m[2].replace(/^\/\//, "");
110
+ if (!p) return raw;
111
+ const isAbs = path.isAbsolute(p) || /^[a-zA-Z]:[\\/]/.test(p);
112
+ const abs = isAbs ? p : path.resolve(userCwd, p);
113
+ return "file:" + abs.split("\\").join("/");
114
+ }
115
+ const runUrl = normalizeDbUrl(url);
116
+ const redacted = url.replace(/\/\/([^:/@]+):([^@]+)@/, "//$1:***@");
117
+ console.log(`▶ quick-outerbase v${VERSION} ${scheme} (${redacted})`);
118
+
119
+ // --- Plataforma nombre del asset del release ---
120
+ const ARCH = process.arch; // x64 | arm64
121
+ const PLAT = process.platform; // win32 | linux | darwin
122
+ // Bundles precompilados: solo Linux y Windows. macOS quedó fuera a propósito
123
+ // (los runners de macOS alargan mucho el CI). En Mac, corré desde el código.
124
+ const SUPPORTED_TARGETS = new Set(["win32-x64", "linux-x64"]);
125
+ const target = `${PLAT}-${ARCH}`;
126
+ if (!SUPPORTED_TARGETS.has(target)) {
127
+ fail(
128
+ `No hay bundle precompilado para tu plataforma (${target}).\n` +
129
+ "Soportadas: win32-x64, linux-x64.\n" +
130
+ "Alternativa: corré desde el código con `npx github:" + REPO + "`."
131
+ );
132
+ }
133
+ const assetName = `quick-outerbase-${target}.tar.gz`;
134
+ const assetUrl = `https://github.com/${REPO}/releases/download/v${VERSION}/${assetName}`;
135
+
136
+ // --- Cache por versión+plataforma ---
137
+ const cacheRoot =
138
+ process.env.QUICK_OUTERBASE_CACHE ||
139
+ path.join(os.homedir() || os.tmpdir(), ".cache", "quick-outerbase");
140
+ const bundleDir = path.join(cacheRoot, `${VERSION}-${target}`);
141
+ const serverJs = path.join(bundleDir, "server.js");
142
+
143
+ async function ensureBundle() {
144
+ if (existsSync(serverJs)) return; // ya cacheado
145
+ mkdirSync(bundleDir, { recursive: true });
146
+ // El .tgz va ADENTRO del dir destino y extraemos con cwd + basename: así
147
+ // tar nunca recibe una ruta con ':' (GNU tar la tomaría como host remoto).
148
+ const innerTgz = path.join(bundleDir, "_bundle.tar.gz");
149
+
150
+ const localOverride = process.env.QUICK_OUTERBASE_BUNDLE;
151
+ if (localOverride) {
152
+ if (!existsSync(localOverride)) fail(`QUICK_OUTERBASE_BUNDLE no existe: ${localOverride}`);
153
+ copyFileSync(localOverride, innerTgz);
154
+ } else {
155
+ await download(assetUrl, innerTgz);
156
+ }
157
+ extractInDir(bundleDir, "_bundle.tar.gz");
158
+ try {
159
+ rmSync(innerTgz, { force: true });
160
+ } catch {
161
+ /* noop */
162
+ }
163
+ }
164
+
165
+ async function download(fromUrl, toFile) {
166
+ let res;
167
+ try {
168
+ res = await fetch(fromUrl, { redirect: "follow" });
169
+ } catch (e) {
170
+ fail(`No pude descargar el runtime (${fromUrl}): ${e.message}`);
171
+ }
172
+ if (!res.ok) {
173
+ fail(
174
+ `No pude descargar el runtime (HTTP ${res.status}) de:\n ${fromUrl}\n` +
175
+ `¿Existe el release v${VERSION} con el asset ${assetName}?`
176
+ );
177
+ }
178
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(toFile));
179
+ }
180
+
181
+ function extractInDir(dir, fname) {
182
+ // tar disponible en Windows 10+ (tar.exe/bsdtar), macOS y Linux. Corremos con
183
+ // cwd=dir y solo el basename → sin rutas con ':' que rompan GNU tar.
184
+ const r = spawnSync("tar", ["-xzf", fname], { cwd: dir, stdio: "inherit" });
185
+ if (r.error || r.status !== 0) {
186
+ fail(
187
+ "Falló la extracción con `tar`. Asegurate de tener `tar` en el PATH " +
188
+ "(Windows 10+ lo trae como tar.exe)."
189
+ );
190
+ }
191
+ }
192
+
193
+ // --- Arranque del server standalone + teardown limpio ---
194
+ async function main() {
195
+ await ensureBundle();
196
+ if (!existsSync(serverJs)) fail(`No encontré server.js en ${bundleDir} tras extraer.`);
197
+
198
+ const child = spawn(process.execPath, [serverJs], {
199
+ cwd: bundleDir,
200
+ stdio: "inherit",
201
+ env: {
202
+ ...process.env,
203
+ DATABASE_URL: runUrl,
204
+ PORT: String(port),
205
+ HOSTNAME: process.env.HOSTNAME || "127.0.0.1",
206
+ FORK_LOCAL: "1",
207
+ },
208
+ });
209
+
210
+ let tearingDown = false;
211
+ function teardown(code = 0) {
212
+ if (tearingDown) return;
213
+ tearingDown = true;
214
+ console.log("\n• Cerrando…");
215
+ try {
216
+ if (isWin) spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
217
+ else process.kill(child.pid, "SIGTERM");
218
+ } catch {
219
+ /* ya muerto */
220
+ }
221
+ if (isWin) {
222
+ try {
223
+ const out = spawnSync("netstat", ["-ano"], { encoding: "utf8" }).stdout || "";
224
+ const pids = new Set();
225
+ for (const line of out.split(/\r?\n/)) {
226
+ if (line.includes(":" + port + " ") && /LISTENING/i.test(line)) {
227
+ const pid = line.trim().split(/\s+/).pop();
228
+ if (pid && /^\d+$/.test(pid)) pids.add(pid);
229
+ }
230
+ }
231
+ for (const pid of pids) spawnSync("taskkill", ["/PID", pid, "/T", "/F"], { stdio: "ignore" });
232
+ } catch {
233
+ /* best-effort */
234
+ }
235
+ }
236
+ console.log("• Listo. Puerto liberado, sin procesos zombie.");
237
+ process.exit(code);
238
+ }
239
+ process.on("SIGINT", () => teardown(0));
240
+ process.on("SIGTERM", () => teardown(0));
241
+ child.on("exit", (code) => {
242
+ if (!tearingDown) teardown(code ?? 0);
243
+ });
244
+
245
+ if (!noOpen) {
246
+ const targetUrl = `http://localhost:${port}/env`;
247
+ const started = Date.now();
248
+ // Puede haber varios requests del poll en vuelo a la vez (el server tarda más
249
+ // que el intervalo en responder el primero). Sin este guard, dos respuestas OK
250
+ // abrían el navegador dos veces. `opened` asegura una sola apertura.
251
+ let opened = false;
252
+ const poll = setInterval(() => {
253
+ if (tearingDown) return clearInterval(poll);
254
+ const req = http.get(
255
+ { host: "localhost", port: Number(port), path: "/env", timeout: 2000 },
256
+ (res) => {
257
+ res.destroy();
258
+ clearInterval(poll);
259
+ if (opened) return;
260
+ opened = true;
261
+ console.log(`✔ Listo en ${targetUrl}`);
262
+ openBrowser(targetUrl);
263
+ }
264
+ );
265
+ req.on("error", () => {
266
+ if (Date.now() - started > 60000) {
267
+ clearInterval(poll);
268
+ console.warn("No confirmé el arranque; abrí manualmente " + targetUrl);
269
+ }
270
+ });
271
+ req.on("timeout", () => req.destroy());
272
+ }, 800);
273
+ }
274
+ }
275
+
276
+ function openBrowser(u) {
277
+ try {
278
+ if (isWin) spawn("cmd", ["/c", "start", "", u], { stdio: "ignore", detached: true });
279
+ else if (process.platform === "darwin") spawn("open", [u], { stdio: "ignore", detached: true });
280
+ else spawn("xdg-open", [u], { stdio: "ignore", detached: true });
281
+ } catch {
282
+ /* sin browser, no es fatal */
283
+ }
284
+ }
285
+
286
+ main().catch((e) => fail("Error: " + (e?.message || e)));
package/package.json CHANGED
@@ -1,33 +1,34 @@
1
- {
2
- "name": "quick-outerbase",
3
- "version": "0.3.6",
4
- "description": "Launcher de quick-outerbase: baja un runtime precompilado y levanta una UI web local para tu DATABASE_URL (postgres/mysql/sqlite/libsql) en segundos. Fork de Outerbase Studio (AGPL-3.0).",
5
- "license": "AGPL-3.0-only",
6
- "bin": {
7
- "quick-outerbase": "launcher.mjs"
8
- },
9
- "type": "module",
10
- "engines": {
11
- "node": ">=20.9.0"
12
- },
13
- "files": [
14
- "launcher.mjs",
15
- "AVISO_LICENCIA.md",
16
- "LICENSE"
17
- ],
18
- "repository": {
19
- "type": "git",
20
- "url": "git+https://github.com/joajo13/quick-outerbase.git"
21
- },
22
- "homepage": "https://github.com/joajo13/quick-outerbase#readme",
23
- "keywords": [
24
- "database",
25
- "studio",
26
- "postgres",
27
- "mysql",
28
- "sqlite",
29
- "libsql",
30
- "prisma-studio",
31
- "gui"
32
- ]
33
- }
1
+ {
2
+ "name": "quick-outerbase",
3
+ "version": "0.4.0",
4
+ "description": "Launcher de quick-outerbase: baja un runtime precompilado y levanta una UI web local para tu DATABASE_URL (postgres/mysql/sqlite/libsql/dynamodb) en segundos. Fork de Outerbase Studio (AGPL-3.0).",
5
+ "license": "AGPL-3.0-only",
6
+ "bin": {
7
+ "quick-outerbase": "launcher.mjs"
8
+ },
9
+ "type": "module",
10
+ "engines": {
11
+ "node": ">=20.9.0"
12
+ },
13
+ "files": [
14
+ "launcher.mjs",
15
+ "AVISO_LICENCIA.md",
16
+ "LICENSE"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/joajo13/quick-outerbase.git"
21
+ },
22
+ "homepage": "https://github.com/joajo13/quick-outerbase#readme",
23
+ "keywords": [
24
+ "database",
25
+ "studio",
26
+ "postgres",
27
+ "mysql",
28
+ "sqlite",
29
+ "libsql",
30
+ "dynamodb",
31
+ "prisma-studio",
32
+ "gui"
33
+ ]
34
+ }