Skip to content

Commit 71cfc61

Browse files
committed
feat(cli): comando jht dashboard — avvia next dev, verifica porta, apri browser cross-platform
2 parents 3365493 + 7197320 commit 71cfc61

2 files changed

Lines changed: 149 additions & 0 deletions

File tree

cli/src/commands/dashboard.js

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { access } from 'node:fs/promises';
2+
import { join } from 'node:path';
3+
import { execSync, spawn } from 'node:child_process';
4+
import { createConnection } from 'node:net';
5+
import { platform } from 'node:os';
6+
7+
const DEFAULT_PORT = 3000;
8+
const DEFAULT_URL = `http://localhost:${DEFAULT_PORT}`;
9+
10+
const GREEN = '\x1b[32m';
11+
const YELLOW = '\x1b[33m';
12+
const DIM = '\x1b[90m';
13+
const BOLD = '\x1b[1m';
14+
const RESET = '\x1b[0m';
15+
16+
function getRepoRoot() {
17+
try { return execSync('git rev-parse --show-toplevel 2>/dev/null', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim(); }
18+
catch { return null; }
19+
}
20+
21+
async function fileExists(p) {
22+
try { await access(p); return true; } catch { return false; }
23+
}
24+
25+
function isPortOpen(port) {
26+
return new Promise((resolve) => {
27+
const conn = createConnection({ port, host: '127.0.0.1' });
28+
conn.on('connect', () => { conn.destroy(); resolve(true); });
29+
conn.on('error', () => { resolve(false); });
30+
conn.setTimeout(1000, () => { conn.destroy(); resolve(false); });
31+
});
32+
}
33+
34+
function openBrowser(url) {
35+
const cmd = platform() === 'darwin' ? 'open' : platform() === 'win32' ? 'start' : 'xdg-open';
36+
try { execSync(`${cmd} "${url}" 2>/dev/null`, { stdio: 'pipe' }); return true; }
37+
catch { return false; }
38+
}
39+
40+
async function findWebDir() {
41+
const root = getRepoRoot();
42+
if (!root) return null;
43+
const webDir = join(root, 'web');
44+
if (await fileExists(join(webDir, 'package.json'))) return webDir;
45+
// Fallback: cerca nella directory corrente
46+
if (await fileExists(join(process.cwd(), 'web', 'package.json'))) return join(process.cwd(), 'web');
47+
return null;
48+
}
49+
50+
function startNextDev(webDir, port) {
51+
const child = spawn('npm', ['run', 'dev', '--', '-p', String(port)], {
52+
cwd: webDir,
53+
detached: true,
54+
stdio: ['ignore', 'pipe', 'pipe'],
55+
env: { ...process.env, PORT: String(port) },
56+
});
57+
child.unref();
58+
return child;
59+
}
60+
61+
async function waitForReady(port, timeoutMs = 15000) {
62+
const start = Date.now();
63+
while (Date.now() - start < timeoutMs) {
64+
if (await isPortOpen(port)) return true;
65+
await new Promise(r => setTimeout(r, 500));
66+
}
67+
return false;
68+
}
69+
70+
async function handleDashboard(options) {
71+
const port = parseInt(options.port ?? String(DEFAULT_PORT), 10) || DEFAULT_PORT;
72+
const url = `http://localhost:${port}`;
73+
74+
console.log(`\n ${BOLD}JHT — Dashboard${RESET}\n`);
75+
76+
// Controlla se la porta è già in uso (dashboard già attiva)
77+
const alreadyRunning = await isPortOpen(port);
78+
79+
if (alreadyRunning) {
80+
console.log(` ${GREEN}${RESET} Dashboard già attiva su ${GREEN}${url}${RESET}`);
81+
if (!options.noBrowser) {
82+
openBrowser(url);
83+
console.log(` ${DIM}Browser aperto.${RESET}`);
84+
}
85+
console.log('');
86+
return;
87+
}
88+
89+
// Trova directory web
90+
const webDir = await findWebDir();
91+
if (!webDir) {
92+
console.error(' Directory web/ non trovata. Assicurati di essere nel repo JHT.');
93+
process.exitCode = 1;
94+
return;
95+
}
96+
97+
// Verifica che npm install sia stato eseguito
98+
if (!(await fileExists(join(webDir, 'node_modules')))) {
99+
console.log(` ${DIM}Installazione dipendenze web...${RESET}`);
100+
try {
101+
execSync('npm install --silent', { cwd: webDir, stdio: ['pipe', 'pipe', 'pipe'] });
102+
console.log(` ${GREEN}${RESET} Dipendenze installate`);
103+
} catch {
104+
console.error(' npm install fallito nella directory web/');
105+
process.exitCode = 1;
106+
return;
107+
}
108+
}
109+
110+
// Avvia next dev in background
111+
console.log(` ${DIM}Avvio Next.js dev server sulla porta ${port}...${RESET}`);
112+
const child = startNextDev(webDir, port);
113+
114+
child.stderr?.on('data', (data) => {
115+
const msg = data.toString().trim();
116+
if (msg.includes('EADDRINUSE')) {
117+
console.error(` \x1b[31mPorta ${port} già in uso da un altro processo.\x1b[0m`);
118+
console.error(` ${DIM}Prova: jht dashboard --port ${port + 1}${RESET}\n`);
119+
}
120+
});
121+
122+
// Aspetta che il server sia pronto
123+
const ready = await waitForReady(port);
124+
125+
if (ready) {
126+
console.log(` ${GREEN}${RESET} Dashboard avviata su ${GREEN}${url}${RESET}`);
127+
console.log(` ${DIM}PID: ${child.pid} (in background)${RESET}`);
128+
if (!options.noBrowser) {
129+
openBrowser(url);
130+
console.log(` ${DIM}Browser aperto.${RESET}`);
131+
}
132+
console.log(`\n ${DIM}Per fermare: kill ${child.pid}${RESET}\n`);
133+
} else {
134+
console.log(` ${YELLOW}Server avviato ma non ancora pronto (PID: ${child.pid}).${RESET}`);
135+
console.log(` ${DIM}Apri manualmente: ${url}${RESET}\n`);
136+
}
137+
}
138+
139+
export function registerDashboardCommand(program) {
140+
program
141+
.command('dashboard')
142+
.alias('web')
143+
.description('Avvia la dashboard web e apri il browser')
144+
.option('-p, --port <port>', 'porta (default 3000)', String(DEFAULT_PORT))
145+
.option('--no-browser', 'non aprire il browser automaticamente')
146+
.action(handleDashboard);
147+
}

cli/src/program.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { registerSessionsCommand } from './commands/sessions.js';
2020
import { registerTemplatesCommand } from './commands/templates.js';
2121
import { registerDoctorCommand } from './commands/doctor.js';
2222
import { registerUpgradeCommand } from './commands/upgrade.js';
23+
import { registerDashboardCommand } from './commands/dashboard.js';
2324

2425
export function buildProgram() {
2526
const program = new Command();
@@ -50,6 +51,7 @@ export function buildProgram() {
5051
registerTemplatesCommand(program);
5152
registerDoctorCommand(program);
5253
registerUpgradeCommand(program);
54+
registerDashboardCommand(program);
5355

5456
return program;
5557
}

0 commit comments

Comments
 (0)