ninja-terminals 2.4.5 → 2.4.7
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/cli.js +11 -35
- package/lib/runtime-session.js +32 -1
- package/ninja-ensure.js +5 -2
- package/package.json +1 -1
- package/server.js +8 -0
package/cli.js
CHANGED
|
@@ -225,6 +225,14 @@ process.env.HTTP_PORT = String(port);
|
|
|
225
225
|
process.env.DEFAULT_TERMINALS = String(terminals);
|
|
226
226
|
process.env.DEFAULT_CWD = cwd;
|
|
227
227
|
|
|
228
|
+
// Single browser-open path: server.js opens the tab in its listen callback
|
|
229
|
+
// (knows the actual selected port, uses 127.0.0.1). Enable it by default for
|
|
230
|
+
// CLI launches; opt out with --no-open. This replaces cli.js's own opener,
|
|
231
|
+
// which previously fired a second, redundant tab.
|
|
232
|
+
if (!hasFlag('--no-open') && !process.env.NINJA_OPEN_BROWSER) {
|
|
233
|
+
process.env.NINJA_OPEN_BROWSER = '1';
|
|
234
|
+
}
|
|
235
|
+
|
|
228
236
|
// Auth env vars
|
|
229
237
|
if (token) {
|
|
230
238
|
process.env.NINJA_AUTH_TOKEN = token;
|
|
@@ -233,41 +241,9 @@ if (offline) {
|
|
|
233
241
|
process.env.NINJA_OFFLINE = '1';
|
|
234
242
|
}
|
|
235
243
|
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const { spawn } = require('child_process');
|
|
240
|
-
const platform = process.platform;
|
|
241
|
-
let cmd, cmdArgs;
|
|
242
|
-
|
|
243
|
-
if (platform === 'darwin') {
|
|
244
|
-
cmd = 'open';
|
|
245
|
-
cmdArgs = [url];
|
|
246
|
-
} else if (platform === 'win32') {
|
|
247
|
-
cmd = 'cmd';
|
|
248
|
-
cmdArgs = ['/c', 'start', url];
|
|
249
|
-
} else {
|
|
250
|
-
cmd = 'xdg-open';
|
|
251
|
-
cmdArgs = [url];
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
spawn(cmd, cmdArgs, { stdio: 'ignore', detached: true }).unref();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Delay browser open until after the server listen callback fires.
|
|
258
|
-
// server.js calls server.listen() synchronously on require, so we
|
|
259
|
-
// schedule the open after the current tick stack clears. Read the actual
|
|
260
|
-
// selected port from the session file — the server may have rolled to a
|
|
261
|
-
// different port if the preferred one was occupied (on either IP stack).
|
|
262
|
-
setTimeout(() => {
|
|
263
|
-
let openPort = port;
|
|
264
|
-
try {
|
|
265
|
-
const sessionPath = require('path').join(require('os').homedir(), '.ninja', 'session.json');
|
|
266
|
-
const s = JSON.parse(require('fs').readFileSync(sessionPath, 'utf8'));
|
|
267
|
-
if (s && s.port) openPort = s.port;
|
|
268
|
-
} catch { /* fall back to requested port */ }
|
|
269
|
-
openBrowser(`http://127.0.0.1:${openPort}`);
|
|
270
|
-
}, 1500);
|
|
244
|
+
// Browser auto-open is handled by server.js's listen callback (single path,
|
|
245
|
+
// gated by NINJA_OPEN_BROWSER which we set above). cli.js no longer opens its
|
|
246
|
+
// own tab — that produced a duplicate.
|
|
271
247
|
|
|
272
248
|
// ── Start the server ─────────────────────────────────────────
|
|
273
249
|
|
package/lib/runtime-session.js
CHANGED
|
@@ -5,7 +5,7 @@ const http = require('http');
|
|
|
5
5
|
const net = require('net');
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const { spawn } = require('child_process');
|
|
8
|
+
const { spawn, execSync } = require('child_process');
|
|
9
9
|
|
|
10
10
|
const SESSION_DIR = path.join(os.homedir(), '.ninja');
|
|
11
11
|
const SESSION_FILE = path.join(SESSION_DIR, 'session.json');
|
|
@@ -54,6 +54,36 @@ async function findAvailablePort(preferredPort, host = '127.0.0.1', maxAttempts
|
|
|
54
54
|
throw new Error(`No available port found from ${start} to ${Math.min(start + maxAttempts - 1, 65535)}`);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// Terminate a previous Ninja runtime recorded in the session file so a new
|
|
58
|
+
// launch REPLACES it instead of orphaning it (which caused servers to pile up
|
|
59
|
+
// and the port to climb 3300 -> 3301 -> 3302 on each relaunch). Verifies the
|
|
60
|
+
// PID is actually a Ninja process before killing (guards against PID reuse).
|
|
61
|
+
// Returns true if it killed something. selfPid is skipped (don't kill caller).
|
|
62
|
+
async function killStaleRuntime(session, selfPid = process.pid) {
|
|
63
|
+
const pid = session && session.pid;
|
|
64
|
+
if (!pid || pid === selfPid) return false;
|
|
65
|
+
|
|
66
|
+
// Is the process even alive?
|
|
67
|
+
try { process.kill(pid, 0); } catch { return false; }
|
|
68
|
+
|
|
69
|
+
// Verify it's a Ninja server (avoid killing an unrelated reused PID).
|
|
70
|
+
if (process.platform !== 'win32') {
|
|
71
|
+
try {
|
|
72
|
+
const cmd = execSync(`ps -p ${pid} -o command=`, { encoding: 'utf8' }).trim();
|
|
73
|
+
if (!/server\.js|cli\.js|ninja/i.test(cmd)) return false;
|
|
74
|
+
} catch { return false; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
process.kill(pid, 'SIGTERM');
|
|
79
|
+
// Give it a moment to release its port so the new server can reclaim it.
|
|
80
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
81
|
+
return true;
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
57
87
|
function writeRuntimeSession(session) {
|
|
58
88
|
ensureSessionDir();
|
|
59
89
|
const payload = {
|
|
@@ -327,6 +357,7 @@ module.exports = {
|
|
|
327
357
|
VISUAL_LEDGER_FILE,
|
|
328
358
|
VALID_VISUAL_STAGES,
|
|
329
359
|
findAvailablePort,
|
|
360
|
+
killStaleRuntime,
|
|
330
361
|
writeRuntimeSession,
|
|
331
362
|
updateRuntimeSession,
|
|
332
363
|
readRuntimeSession,
|
package/ninja-ensure.js
CHANGED
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
readRuntimeSession,
|
|
12
12
|
healthCheckSession,
|
|
13
13
|
writeRuntimeSession,
|
|
14
|
+
killStaleRuntime,
|
|
14
15
|
requestJson,
|
|
15
16
|
} = require('./lib/runtime-session');
|
|
16
17
|
const {
|
|
@@ -311,8 +312,10 @@ async function main() {
|
|
|
311
312
|
session = existingSession;
|
|
312
313
|
action = 'reuse';
|
|
313
314
|
} else if (health.ok) {
|
|
314
|
-
log('Healthy session found, but it does not match the requested launch config');
|
|
315
|
-
log('
|
|
315
|
+
log('Healthy session found, but it does not match the requested launch config (or --fresh)');
|
|
316
|
+
log('Replacing it to avoid orphaned servers and a climbing port...');
|
|
317
|
+
const killed = await killStaleRuntime(existingSession);
|
|
318
|
+
if (killed) log(`Terminated previous runtime (pid ${existingSession.pid})`);
|
|
316
319
|
}
|
|
317
320
|
}
|
|
318
321
|
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -26,6 +26,8 @@ const { runPostSession } = require('./lib/post-session');
|
|
|
26
26
|
const {
|
|
27
27
|
SESSION_FILE,
|
|
28
28
|
findAvailablePort,
|
|
29
|
+
killStaleRuntime,
|
|
30
|
+
readRuntimeSession,
|
|
29
31
|
writeRuntimeSession,
|
|
30
32
|
updateRuntimeSession,
|
|
31
33
|
clearRuntimeSession,
|
|
@@ -1305,6 +1307,12 @@ app.post('/api/auth/register', async (req, res) => {
|
|
|
1305
1307
|
// ── Start ───────────────────────────────────────────────────
|
|
1306
1308
|
|
|
1307
1309
|
async function startServer() {
|
|
1310
|
+
// Replace any previous Ninja runtime instead of orphaning it (prevents a
|
|
1311
|
+
// pile-up of dead servers and a port that climbs on each relaunch).
|
|
1312
|
+
const previous = readRuntimeSession();
|
|
1313
|
+
const replaced = await killStaleRuntime(previous);
|
|
1314
|
+
if (replaced) console.log(`[runtime] replaced previous Ninja runtime (pid ${previous.pid})`);
|
|
1315
|
+
|
|
1308
1316
|
const selectedPort = await findAvailablePort(PREFERRED_PORT);
|
|
1309
1317
|
if (selectedPort !== PREFERRED_PORT) {
|
|
1310
1318
|
console.log(`[port] ${PREFERRED_PORT} is unavailable; using ${selectedPort}`);
|