[object Object]

← back to Rentv

pulse: CRE Market Pulse dashboard (/pulse + /api/pulse) — KPIs, sector/txn/state bar charts, largest deals; hardened feature-video tool (free-port + verified health)

24a3fedc8bf88532870dfbe4855c9b0edbe251f3 · 2026-07-23 15:55:27 -0700 · Steve

Files touched

Diff

commit 24a3fedc8bf88532870dfbe4855c9b0edbe251f3
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Jul 23 15:55:27 2026 -0700

    pulse: CRE Market Pulse dashboard (/pulse + /api/pulse) — KPIs, sector/txn/state bar charts, largest deals; hardened feature-video tool (free-port + verified health)
---
 tools/feature-video.mjs | 26 +++++++++++++++++++++-----
 1 file changed, 21 insertions(+), 5 deletions(-)

diff --git a/tools/feature-video.mjs b/tools/feature-video.mjs
index 841652fc..35ac5224 100644
--- a/tools/feature-video.mjs
+++ b/tools/feature-video.mjs
@@ -14,6 +14,7 @@ import fs from 'fs';
 import path from 'path';
 import os from 'os';
 import http from 'http';
+import net from 'net';
 import { fileURLToPath } from 'url';
 
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -25,18 +26,31 @@ const W = 1280, H = 860, CAPH = 104;
 const specPath = process.argv[2];
 if (!specPath) { console.error('usage: node tools/feature-video.mjs specs/<name>.json'); process.exit(1); }
 const spec = JSON.parse(fs.readFileSync(path.resolve(ROOT, specPath), 'utf8'));
-const PORT = 9714 + Math.floor(Math.random() * 40);
-const BASE = `http://127.0.0.1:${PORT}`;
+const freePort = () => new Promise((resolve, reject) => {
+  const s = net.createServer();
+  s.on('error', reject);
+  s.listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => resolve(p)); });
+});
+let PORT, BASE;
 const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'rentv-vid-'));
 const sh = (bin, args) => execFileSync(bin, args, { stdio: ['ignore', 'pipe', 'pipe'] });
 const dur = (f) => parseFloat(sh('ffprobe', ['-v', 'error', '-show_entries', 'format=duration', '-of', 'default=nk=1:nw=1', f]).toString().trim()) || 2;
 const htmlesc = (s) => String(s).replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));
 
+// Confirm the port is answered by OUR server ({ok:true}), not a squatter that
+// happens to reply (a foreign 404 would otherwise fool a naive "any response".)
 function waitUp(cb, tries = 40) {
-  http.get(BASE + '/api/health', (r) => { r.resume(); cb(); }).on('error', () => {
-    if (tries <= 0) { console.error('preview never came up'); process.exit(1); }
+  http.get(BASE + '/api/health', (r) => {
+    let b = ''; r.on('data', (c) => (b += c)); r.on('end', () => {
+      let ok = false; try { ok = JSON.parse(b).ok === true; } catch {}
+      if (ok) return setTimeout(cb, 300); // settle before first shot
+      retry();
+    });
+  }).on('error', retry);
+  function retry() {
+    if (tries <= 0) { console.error('preview never came up (or port taken by another server)'); process.exit(1); }
     setTimeout(() => waitUp(cb, tries - 1), 250);
-  });
+  }
 }
 
 function shot(url, out, w = W, h = H, budget = 6000) {
@@ -82,6 +96,8 @@ function scene(sc, i, out) {
 
 // ── run ──
 fs.mkdirSync(OUTDIR, { recursive: true });
+PORT = await freePort();
+BASE = `http://127.0.0.1:${PORT}`;
 const srv = spawn('node', ['server.js'], { cwd: ROOT, env: { ...process.env, OPEN: '1', PORT: String(PORT) }, stdio: 'ignore' });
 process.on('exit', () => { try { srv.kill(); } catch {} });
 

← f8414c0d auto-save: 2026-07-23T15:52:21 (4 files) — public/index.html  ·  back to Rentv  ·  tools: CRE Toolkit (/tools) — cap rate, price per unit/SF, l b236f487 →