← back to Rentv 2026
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
M tools/feature-video.mjs
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) => ({ '&': '&', '<': '<', '>': '>' }[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 2026
·
tools: CRE Toolkit (/tools) — cap rate, price per unit/SF, l b236f487 →