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.
- package/launcher.mjs +286 -257
- 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) =>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
async function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
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.
|
|
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
|
-
"
|
|
31
|
-
"
|
|
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
|
+
}
|