@danieltmn/openbridge 0.5.1 → 0.5.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/CHANGELOG.md +10 -0
- package/package.json +1 -1
- package/src/cli.js +84 -0
- package/src/store/jsonfile.js +24 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ Todos los cambios relevantes de OpenBridge. Formato basado en
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/) y
|
|
5
5
|
[Versionado Semantico](https://semver.org/lang/es/).
|
|
6
6
|
|
|
7
|
+
## [0.5.2] - 2026-09-14
|
|
8
|
+
|
|
9
|
+
### Corregido
|
|
10
|
+
|
|
11
|
+
- Diagnostico de puerto ocupado: `status` y `server` muestran que **PID** usa el
|
|
12
|
+
puerto (suele ser otra casa con otro `--dir`) y, si el arranque en segundo plano
|
|
13
|
+
muere, se muestran las ultimas lineas de `server.log`.
|
|
14
|
+
- `store`: reintentos con backoff en el `rename` atomico (EPERM/EACCES/EBUSY en
|
|
15
|
+
Windows) y limpieza del `.tmp` si no se puede.
|
|
16
|
+
|
|
7
17
|
## [0.5.1] - 2026-09-14
|
|
8
18
|
|
|
9
19
|
### Corregido
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -105,6 +105,70 @@ function pidAlive(pid) {
|
|
|
105
105
|
try { process.kill(pid, 0); return true; } catch (e) { return false; }
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// PIDs escuchando en un puerto TCP (netstat en Windows; lsof en el resto). Sirve
|
|
109
|
+
// para explicar un EADDRINUSE: normalmente es OTRA casa (otro --dir) con el
|
|
110
|
+
// mismo puerto por defecto.
|
|
111
|
+
function portOwnerPids(port) {
|
|
112
|
+
try {
|
|
113
|
+
const win = process.platform === 'win32';
|
|
114
|
+
const r = spawnSync(win ? 'netstat' : 'lsof',
|
|
115
|
+
win ? ['-ano', '-p', 'tcp'] : ['-t', '-i', 'tcp:' + port, '-s', 'tcp:listen'],
|
|
116
|
+
{ encoding: 'utf8', windowsHide: true, timeout: 15000 });
|
|
117
|
+
if (r.error || !r.stdout) return [];
|
|
118
|
+
const pids = new Set();
|
|
119
|
+
if (!win) {
|
|
120
|
+
for (const n of String(r.stdout).split(/\s+/)) {
|
|
121
|
+
const p = parseInt(n, 10);
|
|
122
|
+
if (p > 0) pids.add(p);
|
|
123
|
+
}
|
|
124
|
+
return [...pids];
|
|
125
|
+
}
|
|
126
|
+
const re = new RegExp(':' + port + '\\s');
|
|
127
|
+
for (const line of String(r.stdout).split(/\r?\n/)) {
|
|
128
|
+
if (!re.test(line) || !/LISTENING/i.test(line)) continue;
|
|
129
|
+
const parts = line.trim().split(/\s+/);
|
|
130
|
+
const pid = parseInt(parts[parts.length - 1], 10);
|
|
131
|
+
if (pid > 0) pids.add(pid);
|
|
132
|
+
}
|
|
133
|
+
return [...pids];
|
|
134
|
+
} catch (e) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Nombre del proceso de un PID (best-effort) para el diagnostico del puerto.
|
|
140
|
+
function describePid(pid) {
|
|
141
|
+
try {
|
|
142
|
+
if (process.platform === 'win32') {
|
|
143
|
+
const r = spawnSync('tasklist', ['/FI', 'PID eq ' + pid, '/FO', 'CSV', '/NH'], { encoding: 'utf8', windowsHide: true, timeout: 8000 });
|
|
144
|
+
const m = String(r.stdout || '').match(/^"([^"]+)"/m);
|
|
145
|
+
return m ? m[1] : '';
|
|
146
|
+
}
|
|
147
|
+
const r = spawnSync('ps', ['-p', String(pid), '-o', 'comm='], { encoding: 'utf8', windowsHide: true, timeout: 8000 });
|
|
148
|
+
return String(r.stdout || '').trim();
|
|
149
|
+
} catch (e) {
|
|
150
|
+
return '';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// "PID 123 (node.exe)" para el proceso que ocupa el puerto, o '' si no hay.
|
|
155
|
+
function portHint(port) {
|
|
156
|
+
const pids = portOwnerPids(port);
|
|
157
|
+
if (!pids.length) return '';
|
|
158
|
+
return pids.map((p) => 'PID ' + p + (describePid(p) ? ' (' + describePid(p) + ')' : '')).join(', ');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Ultimas `n` lineas no vacias de un archivo (para mostrar el error real del
|
|
162
|
+
// server cuando arranca en segundo plano y muere).
|
|
163
|
+
function tailLines(file, n) {
|
|
164
|
+
try {
|
|
165
|
+
const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/).filter((l) => l.trim() !== '');
|
|
166
|
+
return lines.slice(-n).join('\n');
|
|
167
|
+
} catch (e) {
|
|
168
|
+
return '';
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
108
172
|
function runtimePath() { return paths.p('runtime.json'); }
|
|
109
173
|
function readRuntime() {
|
|
110
174
|
try { return JSON.parse(fs.readFileSync(runtimePath(), 'utf8')); } catch (e) { return null; }
|
|
@@ -328,6 +392,12 @@ async function cmdServer(argv) {
|
|
|
328
392
|
await printStatus();
|
|
329
393
|
if (!rt) {
|
|
330
394
|
console.log('');
|
|
395
|
+
const tail = tailLines(paths.serverLogPath(), 8);
|
|
396
|
+
if (tail) {
|
|
397
|
+
console.log('Ultimas lineas de ' + paths.serverLogPath() + ':');
|
|
398
|
+
console.log(tail);
|
|
399
|
+
console.log('');
|
|
400
|
+
}
|
|
331
401
|
console.log('No pude confirmar el arranque. Revisa: openbridge logs');
|
|
332
402
|
}
|
|
333
403
|
return 0;
|
|
@@ -349,6 +419,12 @@ async function cmdServer(argv) {
|
|
|
349
419
|
} catch (e) {
|
|
350
420
|
if (e && e.code === 'EADDRINUSE') {
|
|
351
421
|
log.error('puerto ' + port + ' ocupado (¿ya corre OpenBridge?). Usa --port <otro> o `openbridge stop`.');
|
|
422
|
+
const hint = portHint(port);
|
|
423
|
+
if (hint) {
|
|
424
|
+
console.error(' lo esta usando: ' + hint);
|
|
425
|
+
console.error(' puede ser otra casa (otro --dir): proba `openbridge stop --dir <esa-casa>`'
|
|
426
|
+
+ (process.platform === 'win32' ? ' o `taskkill /PID ' + portOwnerPids(port)[0] + ' /T /F`.' : '.'));
|
|
427
|
+
}
|
|
352
428
|
return 1;
|
|
353
429
|
}
|
|
354
430
|
throw e;
|
|
@@ -443,6 +519,14 @@ async function printStatus() {
|
|
|
443
519
|
if (paths.exists()) {
|
|
444
520
|
console.log(' carpeta: ' + paths.baseDir());
|
|
445
521
|
console.log(' datos : ' + paths.home());
|
|
522
|
+
try {
|
|
523
|
+
const app = config.readApp();
|
|
524
|
+
const hint = portHint(app.port);
|
|
525
|
+
if (hint) {
|
|
526
|
+
console.log(' aviso : el puerto ' + app.port + ' ya lo usa ' + hint);
|
|
527
|
+
console.log(' suele ser otra casa (otro --dir); cerrala con `openbridge stop --dir <esa-casa>` o usa --port <otro>.');
|
|
528
|
+
}
|
|
529
|
+
} catch (e) { /* sin app.json no hay puerto que mirar */ }
|
|
446
530
|
}
|
|
447
531
|
return false;
|
|
448
532
|
}
|
package/src/store/jsonfile.js
CHANGED
|
@@ -12,6 +12,29 @@ const fs = require('node:fs/promises');
|
|
|
12
12
|
|
|
13
13
|
const locks = new Map();
|
|
14
14
|
|
|
15
|
+
function delay(ms) {
|
|
16
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// En Windows, rename() puede fallar con EPERM/EACCES si otro proceso (antivirus,
|
|
20
|
+
// indexador) tiene el archivo abierto un instante. Reintentamos con backoff y,
|
|
21
|
+
// si no hay forma, limpiamos el .tmp.
|
|
22
|
+
async function renameWithRetry(from, to, tries = 5) {
|
|
23
|
+
for (let i = 0; ; i++) {
|
|
24
|
+
try {
|
|
25
|
+
await fs.rename(from, to);
|
|
26
|
+
return;
|
|
27
|
+
} catch (e) {
|
|
28
|
+
const transient = e && (e.code === 'EPERM' || e.code === 'EACCES' || e.code === 'EBUSY');
|
|
29
|
+
if (!transient || i >= tries - 1) {
|
|
30
|
+
try { await fs.unlink(from); } catch (e2) { /* nada */ }
|
|
31
|
+
throw e;
|
|
32
|
+
}
|
|
33
|
+
await delay(20 * (i + 1));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
15
38
|
function withLock(key, fn) {
|
|
16
39
|
const prev = locks.get(key) || Promise.resolve();
|
|
17
40
|
const run = prev.then(fn, fn);
|
|
@@ -33,7 +56,7 @@ async function readJson(file, fallback) {
|
|
|
33
56
|
async function writeAtomic(file, data) {
|
|
34
57
|
const tmp = file + '.' + process.pid + '.' + Math.random().toString(36).slice(2) + '.tmp';
|
|
35
58
|
await fs.writeFile(tmp, JSON.stringify(data, null, 2));
|
|
36
|
-
await
|
|
59
|
+
await renameWithRetry(tmp, file);
|
|
37
60
|
}
|
|
38
61
|
|
|
39
62
|
/**
|