create-caspian-app 0.2.0-beta.44 → 0.2.0-beta.45

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.
@@ -1,5 +1,6 @@
1
1
  import { writeFileSync, existsSync, mkdirSync, readFileSync } from "fs";
2
2
  import browserSync, { BrowserSyncInstance } from "browser-sync";
3
+ import { execFile } from "child_process";
3
4
  import { generateFileListJson } from "./files-list.js";
4
5
  import { join, dirname, relative } from "path";
5
6
  import { getFileMeta, PUBLIC_DIR, SRC_DIR } from "./utils.js";
@@ -10,9 +11,10 @@ import {
10
11
  waitForPort,
11
12
  } from "./python-server.js";
12
13
  import { componentMap } from "./component-map.js";
13
- import { createServer } from "net";
14
+ import { Socket } from "net";
14
15
  import chalk from "chalk";
15
- import { networkInterfaces } from "os";
16
+ import { networkInterfaces, platform } from "os";
17
+ import { promisify } from "util";
16
18
  import caspianConfig from "../caspian.config.json";
17
19
 
18
20
  const { __dirname } = getFileMeta();
@@ -25,6 +27,12 @@ let lastChangedFile: string | null = null;
25
27
 
26
28
  let pythonPort = 0;
27
29
  let bsPort = 0;
30
+ const execFileAsync = promisify(execFile);
31
+ const PORT_PROBE_HOST = "127.0.0.1";
32
+
33
+ function isWindows(): boolean {
34
+ return platform() === "win32";
35
+ }
28
36
 
29
37
  function getReservedPorts(): Set<number> {
30
38
  const reservedPorts = new Set<number>();
@@ -59,26 +67,80 @@ function getReservedPorts(): Set<number> {
59
67
  return reservedPorts;
60
68
  }
61
69
 
62
- function getAvailablePort(
63
- startPort: number,
64
- reservedPorts: Set<number> = new Set(),
65
- ): Promise<number> {
70
+ function hasLoopbackListener(port: number): Promise<boolean> {
66
71
  return new Promise((resolve) => {
67
- if (reservedPorts.has(startPort)) {
68
- resolve(getAvailablePort(startPort + 1, reservedPorts));
69
- return;
70
- }
72
+ const socket = new Socket();
73
+ socket.setTimeout(250);
71
74
 
72
- const server = createServer();
73
- server.listen(startPort, () => {
74
- server.close(() => resolve(startPort));
75
+ socket.on("connect", () => {
76
+ socket.destroy();
77
+ resolve(true);
75
78
  });
76
- server.on("error", () => {
77
- resolve(getAvailablePort(startPort + 1, reservedPorts));
79
+
80
+ socket.on("timeout", () => {
81
+ socket.destroy();
82
+ resolve(false);
78
83
  });
84
+
85
+ socket.on("error", () => {
86
+ socket.destroy();
87
+ resolve(false);
88
+ });
89
+
90
+ socket.connect(port, PORT_PROBE_HOST);
79
91
  });
80
92
  }
81
93
 
94
+ async function hasWindowsTcpListener(port: number): Promise<boolean> {
95
+ try {
96
+ const { stdout } = await execFileAsync(
97
+ "netstat",
98
+ ["-ano", "-p", "TCP"],
99
+ { windowsHide: true },
100
+ );
101
+
102
+ return stdout.split(/\r?\n/).some((line) => {
103
+ const parts = line.trim().split(/\s+/);
104
+ return (
105
+ parts.length >= 4 &&
106
+ parts[0].toUpperCase() === "TCP" &&
107
+ parts[1].endsWith(`:${port}`) &&
108
+ parts[3].toUpperCase() === "LISTENING"
109
+ );
110
+ });
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+
116
+ async function isPortAvailable(
117
+ port: number,
118
+ reservedPorts: Set<number>,
119
+ ): Promise<boolean> {
120
+ if (reservedPorts.has(port)) {
121
+ return false;
122
+ }
123
+
124
+ if (isWindows() && (await hasWindowsTcpListener(port))) {
125
+ return false;
126
+ }
127
+
128
+ return !(await hasLoopbackListener(port));
129
+ }
130
+
131
+ async function getAvailablePort(
132
+ startPort: number,
133
+ reservedPorts: Set<number> = new Set(),
134
+ ): Promise<number> {
135
+ let port = startPort;
136
+
137
+ while (!(await isPortAvailable(port, reservedPorts))) {
138
+ port += 1;
139
+ }
140
+
141
+ return port;
142
+ }
143
+
82
144
  function getExternalIP(): string | null {
83
145
  const nets = networkInterfaces();
84
146
  for (const name of Object.keys(nets)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-caspian-app",
3
- "version": "0.2.0-beta.44",
3
+ "version": "0.2.0-beta.45",
4
4
  "description": "Scaffold a new Caspian project (FastAPI-powered reactive Python framework).",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",