File size: 2,167 Bytes
5710dd0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#!/usr/bin/env node
// Wraps `concurrently` so the vis-server (API) and vite (web) each pick a
// free port and agree on each other's, even when the defaults are taken by
// a previous dev session or another local app.

import { spawn } from 'node:child_process';
import net from 'node:net';

const DEFAULT_API_PORT = 5174;
const DEFAULT_WEB_PORT = 5173;
const MAX_PROBE = 50;

async function isFree(port) {
  return new Promise((resolve) => {
    const srv = net.createServer();
    srv.unref();
    srv.once('error', () => {
      resolve(false);
    });
    srv.once('listening', () => {
      srv.close(() => {
        resolve(true);
      });
    });
    srv.listen({ port, host: '127.0.0.1', exclusive: true });
  });
}

async function pickPort(startPort, exclude = new Set()) {
  for (let port = startPort; port < startPort + MAX_PROBE; port += 1) {
    if (exclude.has(port)) continue;
    // eslint-disable-next-line no-await-in-loop
    if (await isFree(port)) return port;
  }
  throw new Error(
    `no free port in [${startPort}, ${startPort + MAX_PROBE}); something is hogging the range`,
  );
}

const requestedApi = Number(process.env.PORT) || DEFAULT_API_PORT;
const apiPort = await pickPort(requestedApi);
if (apiPort !== requestedApi) {
  process.stdout.write(`[vis] api port ${requestedApi} busy, using ${apiPort} instead\n`);
}

const requestedWeb = Number(process.env.WEB_PORT) || DEFAULT_WEB_PORT;
const webPort = await pickPort(requestedWeb, new Set([apiPort]));
if (webPort !== requestedWeb) {
  process.stdout.write(`[vis] web port ${requestedWeb} busy, using ${webPort} instead\n`);
}

process.stdout.write(`[vis] web → http://localhost:${webPort}  (api on ${apiPort})\n`);

const env = { ...process.env, PORT: String(apiPort), WEB_PORT: String(webPort) };
const child = spawn(
  'concurrently',
  [
    '-k',
    '-n', 'server,web',
    '-c', 'cyan,magenta',
    'pnpm --filter @moonshot-ai/vis-server dev',
    'pnpm --filter @moonshot-ai/vis-web dev',
  ],
  { stdio: 'inherit', env, shell: false },
);

child.on('exit', (code, signal) => {
  if (signal !== null) process.kill(process.pid, signal);
  else process.exit(code ?? 0);
});