@superdangerous/app-framework 4.9.0

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.
Files changed (239) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +652 -0
  3. package/dist/api/logsRouter.d.ts +20 -0
  4. package/dist/api/logsRouter.d.ts.map +1 -0
  5. package/dist/api/logsRouter.js +515 -0
  6. package/dist/api/logsRouter.js.map +1 -0
  7. package/dist/cli/dev-server.d.ts +7 -0
  8. package/dist/cli/dev-server.d.ts.map +1 -0
  9. package/dist/cli/dev-server.js +640 -0
  10. package/dist/cli/dev-server.js.map +1 -0
  11. package/dist/cli/index.d.ts +7 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +26 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/core/StandardServer.d.ts +129 -0
  16. package/dist/core/StandardServer.d.ts.map +1 -0
  17. package/dist/core/StandardServer.js +453 -0
  18. package/dist/core/StandardServer.js.map +1 -0
  19. package/dist/core/apiResponse.d.ts +69 -0
  20. package/dist/core/apiResponse.d.ts.map +1 -0
  21. package/dist/core/apiResponse.js +127 -0
  22. package/dist/core/apiResponse.js.map +1 -0
  23. package/dist/core/healthCheck.d.ts +160 -0
  24. package/dist/core/healthCheck.d.ts.map +1 -0
  25. package/dist/core/healthCheck.js +398 -0
  26. package/dist/core/healthCheck.js.map +1 -0
  27. package/dist/core/index.d.ts +40 -0
  28. package/dist/core/index.d.ts.map +1 -0
  29. package/dist/core/index.js +40 -0
  30. package/dist/core/index.js.map +1 -0
  31. package/dist/core/logger.d.ts +117 -0
  32. package/dist/core/logger.d.ts.map +1 -0
  33. package/dist/core/logger.js +826 -0
  34. package/dist/core/logger.js.map +1 -0
  35. package/dist/core/portUtils.d.ts +71 -0
  36. package/dist/core/portUtils.d.ts.map +1 -0
  37. package/dist/core/portUtils.js +240 -0
  38. package/dist/core/portUtils.js.map +1 -0
  39. package/dist/core/storageService.d.ts +119 -0
  40. package/dist/core/storageService.d.ts.map +1 -0
  41. package/dist/core/storageService.js +405 -0
  42. package/dist/core/storageService.js.map +1 -0
  43. package/dist/desktop/bundler.d.ts +40 -0
  44. package/dist/desktop/bundler.d.ts.map +1 -0
  45. package/dist/desktop/bundler.js +176 -0
  46. package/dist/desktop/bundler.js.map +1 -0
  47. package/dist/desktop/index.d.ts +25 -0
  48. package/dist/desktop/index.d.ts.map +1 -0
  49. package/dist/desktop/index.js +15 -0
  50. package/dist/desktop/index.js.map +1 -0
  51. package/dist/desktop/native-modules.d.ts +66 -0
  52. package/dist/desktop/native-modules.d.ts.map +1 -0
  53. package/dist/desktop/native-modules.js +200 -0
  54. package/dist/desktop/native-modules.js.map +1 -0
  55. package/dist/index.d.ts +29 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +39 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/logging/LogCategories.d.ts +87 -0
  60. package/dist/logging/LogCategories.d.ts.map +1 -0
  61. package/dist/logging/LogCategories.js +205 -0
  62. package/dist/logging/LogCategories.js.map +1 -0
  63. package/dist/middleware/aiErrorHandler.d.ts +31 -0
  64. package/dist/middleware/aiErrorHandler.d.ts.map +1 -0
  65. package/dist/middleware/aiErrorHandler.js +181 -0
  66. package/dist/middleware/aiErrorHandler.js.map +1 -0
  67. package/dist/middleware/auth.d.ts +101 -0
  68. package/dist/middleware/auth.d.ts.map +1 -0
  69. package/dist/middleware/auth.js +230 -0
  70. package/dist/middleware/auth.js.map +1 -0
  71. package/dist/middleware/cors.d.ts +56 -0
  72. package/dist/middleware/cors.d.ts.map +1 -0
  73. package/dist/middleware/cors.js +123 -0
  74. package/dist/middleware/cors.js.map +1 -0
  75. package/dist/middleware/errorHandler.d.ts +13 -0
  76. package/dist/middleware/errorHandler.d.ts.map +1 -0
  77. package/dist/middleware/errorHandler.js +85 -0
  78. package/dist/middleware/errorHandler.js.map +1 -0
  79. package/dist/middleware/fileUpload.d.ts +62 -0
  80. package/dist/middleware/fileUpload.d.ts.map +1 -0
  81. package/dist/middleware/fileUpload.js +175 -0
  82. package/dist/middleware/fileUpload.js.map +1 -0
  83. package/dist/middleware/health.d.ts +48 -0
  84. package/dist/middleware/health.d.ts.map +1 -0
  85. package/dist/middleware/health.js +143 -0
  86. package/dist/middleware/health.js.map +1 -0
  87. package/dist/middleware/index.d.ts +20 -0
  88. package/dist/middleware/index.d.ts.map +1 -0
  89. package/dist/middleware/index.js +18 -0
  90. package/dist/middleware/index.js.map +1 -0
  91. package/dist/middleware/openapi.d.ts +64 -0
  92. package/dist/middleware/openapi.d.ts.map +1 -0
  93. package/dist/middleware/openapi.js +258 -0
  94. package/dist/middleware/openapi.js.map +1 -0
  95. package/dist/middleware/requestLogging.d.ts +22 -0
  96. package/dist/middleware/requestLogging.d.ts.map +1 -0
  97. package/dist/middleware/requestLogging.js +61 -0
  98. package/dist/middleware/requestLogging.js.map +1 -0
  99. package/dist/middleware/session.d.ts +84 -0
  100. package/dist/middleware/session.d.ts.map +1 -0
  101. package/dist/middleware/session.js +189 -0
  102. package/dist/middleware/session.js.map +1 -0
  103. package/dist/middleware/validation.d.ts +1337 -0
  104. package/dist/middleware/validation.d.ts.map +1 -0
  105. package/dist/middleware/validation.js +483 -0
  106. package/dist/middleware/validation.js.map +1 -0
  107. package/dist/services/aiService.d.ts +180 -0
  108. package/dist/services/aiService.d.ts.map +1 -0
  109. package/dist/services/aiService.js +547 -0
  110. package/dist/services/aiService.js.map +1 -0
  111. package/dist/services/conversationStorage.d.ts +38 -0
  112. package/dist/services/conversationStorage.d.ts.map +1 -0
  113. package/dist/services/conversationStorage.js +158 -0
  114. package/dist/services/conversationStorage.js.map +1 -0
  115. package/dist/services/crossPlatformBuffer.d.ts +84 -0
  116. package/dist/services/crossPlatformBuffer.d.ts.map +1 -0
  117. package/dist/services/crossPlatformBuffer.js +246 -0
  118. package/dist/services/crossPlatformBuffer.js.map +1 -0
  119. package/dist/services/index.d.ts +17 -0
  120. package/dist/services/index.d.ts.map +1 -0
  121. package/dist/services/index.js +18 -0
  122. package/dist/services/index.js.map +1 -0
  123. package/dist/services/networkService.d.ts +81 -0
  124. package/dist/services/networkService.d.ts.map +1 -0
  125. package/dist/services/networkService.js +268 -0
  126. package/dist/services/networkService.js.map +1 -0
  127. package/dist/services/queueService.d.ts +112 -0
  128. package/dist/services/queueService.d.ts.map +1 -0
  129. package/dist/services/queueService.js +338 -0
  130. package/dist/services/queueService.js.map +1 -0
  131. package/dist/services/settingsService.d.ts +135 -0
  132. package/dist/services/settingsService.d.ts.map +1 -0
  133. package/dist/services/settingsService.js +425 -0
  134. package/dist/services/settingsService.js.map +1 -0
  135. package/dist/services/systemMonitor.d.ts +208 -0
  136. package/dist/services/systemMonitor.d.ts.map +1 -0
  137. package/dist/services/systemMonitor.js +693 -0
  138. package/dist/services/systemMonitor.js.map +1 -0
  139. package/dist/services/updateService.d.ts +78 -0
  140. package/dist/services/updateService.d.ts.map +1 -0
  141. package/dist/services/updateService.js +252 -0
  142. package/dist/services/updateService.js.map +1 -0
  143. package/dist/services/websocketEvents.d.ts +372 -0
  144. package/dist/services/websocketEvents.d.ts.map +1 -0
  145. package/dist/services/websocketEvents.js +338 -0
  146. package/dist/services/websocketEvents.js.map +1 -0
  147. package/dist/services/websocketServer.d.ts +80 -0
  148. package/dist/services/websocketServer.d.ts.map +1 -0
  149. package/dist/services/websocketServer.js +299 -0
  150. package/dist/services/websocketServer.js.map +1 -0
  151. package/dist/settings/SettingsSchema.d.ts +151 -0
  152. package/dist/settings/SettingsSchema.d.ts.map +1 -0
  153. package/dist/settings/SettingsSchema.js +424 -0
  154. package/dist/settings/SettingsSchema.js.map +1 -0
  155. package/dist/testing/TestServer.d.ts +69 -0
  156. package/dist/testing/TestServer.d.ts.map +1 -0
  157. package/dist/testing/TestServer.js +250 -0
  158. package/dist/testing/TestServer.js.map +1 -0
  159. package/dist/types/index.d.ts +137 -0
  160. package/dist/types/index.d.ts.map +1 -0
  161. package/dist/types/index.js +5 -0
  162. package/dist/types/index.js.map +1 -0
  163. package/dist/utils/appPaths.d.ts +74 -0
  164. package/dist/utils/appPaths.d.ts.map +1 -0
  165. package/dist/utils/appPaths.js +162 -0
  166. package/dist/utils/appPaths.js.map +1 -0
  167. package/dist/utils/fs-utils.d.ts +50 -0
  168. package/dist/utils/fs-utils.d.ts.map +1 -0
  169. package/dist/utils/fs-utils.js +114 -0
  170. package/dist/utils/fs-utils.js.map +1 -0
  171. package/dist/utils/index.d.ts +12 -0
  172. package/dist/utils/index.d.ts.map +1 -0
  173. package/dist/utils/index.js +10 -0
  174. package/dist/utils/index.js.map +1 -0
  175. package/dist/utils/standardConfig.d.ts +61 -0
  176. package/dist/utils/standardConfig.d.ts.map +1 -0
  177. package/dist/utils/standardConfig.js +109 -0
  178. package/dist/utils/standardConfig.js.map +1 -0
  179. package/dist/utils/startupBanner.d.ts +34 -0
  180. package/dist/utils/startupBanner.d.ts.map +1 -0
  181. package/dist/utils/startupBanner.js +169 -0
  182. package/dist/utils/startupBanner.js.map +1 -0
  183. package/dist/utils/startupLogger.d.ts +45 -0
  184. package/dist/utils/startupLogger.d.ts.map +1 -0
  185. package/dist/utils/startupLogger.js +200 -0
  186. package/dist/utils/startupLogger.js.map +1 -0
  187. package/package.json +151 -0
  188. package/src/api/logsRouter.ts +600 -0
  189. package/src/cli/dev-server.ts +803 -0
  190. package/src/cli/index.ts +31 -0
  191. package/src/core/StandardServer.ts +587 -0
  192. package/src/core/apiResponse.ts +202 -0
  193. package/src/core/healthCheck.ts +565 -0
  194. package/src/core/index.ts +80 -0
  195. package/src/core/logger.ts +1092 -0
  196. package/src/core/portUtils.ts +319 -0
  197. package/src/core/storageService.ts +595 -0
  198. package/src/desktop/bundler.ts +271 -0
  199. package/src/desktop/index.ts +18 -0
  200. package/src/desktop/native-modules.ts +289 -0
  201. package/src/index.ts +142 -0
  202. package/src/logging/LogCategories.ts +302 -0
  203. package/src/middleware/aiErrorHandler.ts +278 -0
  204. package/src/middleware/auth.ts +329 -0
  205. package/src/middleware/cors.ts +187 -0
  206. package/src/middleware/errorHandler.ts +103 -0
  207. package/src/middleware/fileUpload.ts +252 -0
  208. package/src/middleware/health.ts +206 -0
  209. package/src/middleware/index.ts +71 -0
  210. package/src/middleware/openapi.ts +305 -0
  211. package/src/middleware/requestLogging.ts +92 -0
  212. package/src/middleware/session.ts +238 -0
  213. package/src/middleware/validation.ts +603 -0
  214. package/src/services/aiService.ts +789 -0
  215. package/src/services/conversationStorage.ts +232 -0
  216. package/src/services/crossPlatformBuffer.ts +341 -0
  217. package/src/services/index.ts +47 -0
  218. package/src/services/networkService.ts +351 -0
  219. package/src/services/queueService.ts +446 -0
  220. package/src/services/settingsService.ts +549 -0
  221. package/src/services/systemMonitor.ts +936 -0
  222. package/src/services/updateService.ts +334 -0
  223. package/src/services/websocketEvents.ts +409 -0
  224. package/src/services/websocketServer.ts +394 -0
  225. package/src/settings/SettingsSchema.ts +664 -0
  226. package/src/testing/TestServer.ts +312 -0
  227. package/src/types/index.ts +154 -0
  228. package/src/utils/appPaths.ts +196 -0
  229. package/src/utils/fs-utils.ts +130 -0
  230. package/src/utils/index.ts +15 -0
  231. package/src/utils/standardConfig.ts +178 -0
  232. package/src/utils/startupBanner.ts +287 -0
  233. package/src/utils/startupLogger.ts +268 -0
  234. package/ui/dist/index.d.mts +1221 -0
  235. package/ui/dist/index.d.ts +1221 -0
  236. package/ui/dist/index.js +73 -0
  237. package/ui/dist/index.js.map +1 -0
  238. package/ui/dist/index.mjs +73 -0
  239. package/ui/dist/index.mjs.map +1 -0
@@ -0,0 +1,640 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Development server orchestrator for framework apps
4
+ * Manages both backend and frontend processes with unified output
5
+ */
6
+ import { spawn } from "child_process";
7
+ import fs from "fs";
8
+ import path from "path";
9
+ import net from "net";
10
+ import chalk from "chalk";
11
+ import boxen from "boxen";
12
+ import { displayStartupBanner } from "../utils/startupBanner.js";
13
+ import { createLogger } from "../core/logger.js";
14
+ const logger = createLogger("dev-server");
15
+ class DevServerOrchestrator {
16
+ config;
17
+ backendProcess = null;
18
+ frontendProcess = null;
19
+ startTime;
20
+ isBackendReady = null;
21
+ isFrontendReady = false;
22
+ hasDetectedBackendReady = false;
23
+ hasDetectedFrontendReady = false;
24
+ hasShownBanner = false;
25
+ retryCount = 0;
26
+ outputBuffer = [];
27
+ frontendError = null;
28
+ constructor() {
29
+ // Load config from package.json
30
+ const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8"));
31
+ // Smart defaults based on common patterns
32
+ const defaultBackendCommand = fs.existsSync("src/server/index.ts")
33
+ ? "tsx watch src/server/index.ts"
34
+ : fs.existsSync("src/index.ts")
35
+ ? "tsx watch src/index.ts"
36
+ : "tsx watch src/server.ts";
37
+ const defaultFrontendCommand = fs.existsSync("web/package.json")
38
+ ? "cd web && npm run dev"
39
+ : "vite";
40
+ // Get ports from environment or package.json or defaults
41
+ const defaultBackendPort = process.env.PORT
42
+ ? parseInt(process.env.PORT)
43
+ : packageJson.devServer?.backendPort || 3000;
44
+ this.config = {
45
+ appName: packageJson.name || "SuperDangerous App",
46
+ appVersion: packageJson.version || "0.0.0",
47
+ packageName: packageJson.name,
48
+ description: packageJson.description,
49
+ backendPort: defaultBackendPort,
50
+ frontendPort: packageJson.devServer?.frontendPort || 5173,
51
+ webSocketPort: packageJson.devServer?.webSocketPort || defaultBackendPort,
52
+ backendCommand: packageJson.devServer?.backendCommand || defaultBackendCommand,
53
+ frontendCommand: packageJson.devServer?.frontendCommand || defaultFrontendCommand,
54
+ };
55
+ // Set process title for easier identification
56
+ process.title = `dev-server:${this.config.appName}:${this.config.backendPort}`;
57
+ this.startTime = Date.now();
58
+ }
59
+ showStartupBanner() {
60
+ // Only print once both services are ready
61
+ if (!this.isBackendReady || !this.isFrontendReady || this.hasShownBanner)
62
+ return;
63
+ logger.info("Clearing console for startup banner");
64
+ console.clear();
65
+ // Use the standardized banner from utils
66
+ displayStartupBanner({
67
+ appName: this.config.appName,
68
+ appVersion: this.config.appVersion,
69
+ packageName: this.config.packageName,
70
+ description: this.config.description,
71
+ port: this.config.backendPort,
72
+ webPort: this.config.frontendPort,
73
+ webSocketPort: this.config.webSocketPort,
74
+ environment: process.env.NODE_ENV || "development",
75
+ startTime: this.startTime,
76
+ });
77
+ this.hasShownBanner = true;
78
+ // Show any buffered important messages
79
+ if (this.outputBuffer.length > 0) {
80
+ logger.info("\n" + chalk.gray("Recent activity:"));
81
+ this.outputBuffer.forEach((msg) => logger.info(msg));
82
+ this.outputBuffer = [];
83
+ }
84
+ }
85
+ parseCommand(command) {
86
+ const parts = command.split(" ");
87
+ return {
88
+ cmd: parts[0],
89
+ args: parts.slice(1),
90
+ };
91
+ }
92
+ startBackend() {
93
+ logger.info(chalk.gray("Starting backend on port " + this.config.backendPort + "..."));
94
+ const { cmd, args } = this.parseCommand(this.config.backendCommand);
95
+ this.backendProcess = spawn(cmd, args, {
96
+ stdio: ["inherit", "pipe", "pipe"],
97
+ shell: true,
98
+ detached: true, // Create a new process group
99
+ env: {
100
+ ...process.env,
101
+ LOG_LEVEL: "warn", // Only show warnings and errors
102
+ NODE_ENV: "development",
103
+ FORCE_COLOR: "3",
104
+ PORT: this.config.backendPort.toString(),
105
+ APP_NAME: this.config.appName,
106
+ APP_PORT: this.config.backendPort.toString(),
107
+ },
108
+ });
109
+ this.backendProcess.stdout?.on("data", (data) => {
110
+ const output = data.toString();
111
+ // Look for ANY indication the server is ready
112
+ if (!this.hasDetectedBackendReady) {
113
+ // Check for various ready indicators
114
+ if (output.includes("Ready in") ||
115
+ output.includes("Server is running") ||
116
+ output.includes("Server started") ||
117
+ output.includes("Listening on") ||
118
+ output.includes(`${this.config.backendPort}`) ||
119
+ output.includes("✓")) {
120
+ this.hasDetectedBackendReady = true;
121
+ this.isBackendReady = true;
122
+ this.showStartupBanner();
123
+ return;
124
+ }
125
+ }
126
+ // Check for port conflict and show enhanced error
127
+ if (output.includes("EADDRINUSE") || output.includes("already in use")) {
128
+ this.showPortConflictError(this.config.backendPort, "backend");
129
+ }
130
+ // Show errors
131
+ if (output.toLowerCase().includes("error") &&
132
+ !output.includes("ExperimentalWarning")) {
133
+ const lines = output.trim().split("\n");
134
+ lines.forEach((line) => {
135
+ if (line.trim()) {
136
+ const msg = chalk.red(`[Backend] ${line.trim()}`);
137
+ if (this.hasShownBanner) {
138
+ logger.error(msg);
139
+ }
140
+ else {
141
+ this.outputBuffer.push(msg);
142
+ }
143
+ }
144
+ });
145
+ }
146
+ });
147
+ this.backendProcess.stderr?.on("data", (data) => {
148
+ const output = data.toString();
149
+ // Check for port conflict
150
+ if (output.includes("EADDRINUSE") ||
151
+ output.includes("address already in use")) {
152
+ this.showPortConflictError(this.config.backendPort, "backend");
153
+ }
154
+ // For backend stderr, just pass it through as-is to preserve formatting
155
+ // The backend logger already handles coloring appropriately
156
+ if (output.trim() && !output.includes("ExperimentalWarning")) {
157
+ process.stderr.write(output);
158
+ }
159
+ });
160
+ // Secondary readiness check: wait for port to open in case stdout is quiet
161
+ this.waitForPort(this.config.backendPort, 8000).then((reachable) => {
162
+ if (!this.hasDetectedBackendReady && reachable) {
163
+ this.hasDetectedBackendReady = true;
164
+ this.isBackendReady = true;
165
+ this.showStartupBanner();
166
+ }
167
+ });
168
+ this.backendProcess.on("error", (error) => {
169
+ logger.error(chalk.red(`[Backend] Failed to start: ${error.message}`));
170
+ });
171
+ this.backendProcess.on("exit", (code) => {
172
+ if (code !== 0 && code !== null) {
173
+ this.isBackendReady = false;
174
+ logger.error(chalk.red(`[Backend] Exited with code ${code}`));
175
+ }
176
+ });
177
+ // Fallback: Mark as ready after timeout if we haven't detected it
178
+ setTimeout(() => {
179
+ if (!this.hasDetectedBackendReady) {
180
+ if (this.isBackendReady !== false) {
181
+ logger.warn(chalk.yellow("Backend ready detection timeout - assuming ready"));
182
+ this.hasDetectedBackendReady = true;
183
+ this.isBackendReady = true;
184
+ this.showStartupBanner();
185
+ }
186
+ else {
187
+ logger.error(chalk.red("\nBackend failed to start. Check the error messages above."));
188
+ this.cleanup();
189
+ process.exit(1);
190
+ }
191
+ }
192
+ }, 5000);
193
+ }
194
+ async startFrontend() {
195
+ logger.info(chalk.gray("Starting frontend on port " + this.config.frontendPort + "..."));
196
+ // Quick check if port is available before spawning process
197
+ // This helps catch port conflicts faster than waiting for Vite to report them
198
+ const portAvailable = await this.checkPort(this.config.frontendPort);
199
+ if (!portAvailable) {
200
+ this.frontendError = `Port ${this.config.frontendPort} is already in use`;
201
+ // Don't call showPortConflictError here as it exits immediately
202
+ // Instead, simulate a frontend exit to trigger the error banner
203
+ setTimeout(() => {
204
+ if (this.frontendProcess) {
205
+ this.frontendProcess.emit("exit", 1);
206
+ }
207
+ else {
208
+ // If no process created yet, show error directly
209
+ console.clear();
210
+ logger.error(boxen(chalk.red("⚠️ Frontend Failed to Start!\n\n") +
211
+ chalk.white(this.frontendError + "\n\n") +
212
+ chalk.gray("To fix this issue:\n") +
213
+ chalk.gray(`1. Stop the other process using port ${this.config.frontendPort}\n`) +
214
+ chalk.gray(`2. Check what's using it: ${chalk.cyan(`lsof -i :${this.config.frontendPort}`)}\n`) +
215
+ chalk.gray(`3. Kill it with: ${chalk.cyan(`kill -9 <PID>`)}\n`) +
216
+ chalk.gray(`4. Or use a different port in your configuration`), {
217
+ padding: 1,
218
+ margin: 1,
219
+ borderStyle: "round",
220
+ borderColor: "red",
221
+ }));
222
+ this.cleanup();
223
+ process.exit(1);
224
+ }
225
+ }, 100);
226
+ return;
227
+ }
228
+ // Handle different frontend command formats
229
+ let cmd;
230
+ let args;
231
+ if (this.config.frontendCommand.includes("cd ")) {
232
+ // Complex command like "cd web && npm run dev"
233
+ // Extract the directory and command
234
+ const cdMatch = this.config.frontendCommand.match(/cd\s+([^\s&]+)\s*&&\s*(.+)/);
235
+ if (cdMatch) {
236
+ const [, dir, actualCmd] = cdMatch;
237
+ // Run the command in the specified directory
238
+ const parsed = this.parseCommand(actualCmd);
239
+ cmd = parsed.cmd;
240
+ args = parsed.args;
241
+ // Override cwd below to run in the subdirectory
242
+ this.frontendProcess = spawn(cmd, args, {
243
+ stdio: ["inherit", "pipe", "pipe"],
244
+ shell: true,
245
+ detached: true,
246
+ cwd: path.join(process.cwd(), dir), // Run in the subdirectory
247
+ env: {
248
+ ...process.env,
249
+ NODE_ENV: "development",
250
+ FORCE_COLOR: "3",
251
+ },
252
+ });
253
+ }
254
+ else {
255
+ // Fallback to sh -c for complex commands
256
+ cmd = "sh";
257
+ args = ["-c", this.config.frontendCommand];
258
+ logger.info(chalk.gray(`Frontend command: ${cmd} ${args.join(" ")}`));
259
+ this.frontendProcess = spawn(cmd, args, {
260
+ stdio: ["inherit", "pipe", "pipe"],
261
+ shell: true,
262
+ detached: true,
263
+ cwd: process.cwd(),
264
+ env: {
265
+ ...process.env,
266
+ NODE_ENV: "development",
267
+ FORCE_COLOR: "3",
268
+ },
269
+ });
270
+ }
271
+ }
272
+ else if (this.config.frontendCommand === "vite") {
273
+ // Direct vite command
274
+ cmd = "npx";
275
+ args = ["vite", "--port", this.config.frontendPort.toString()];
276
+ }
277
+ else {
278
+ // Parse other commands
279
+ const parsed = this.parseCommand(this.config.frontendCommand);
280
+ cmd = parsed.cmd;
281
+ args = parsed.args;
282
+ }
283
+ // Only spawn if not already created above for cd commands
284
+ if (!this.frontendProcess) {
285
+ this.frontendProcess = spawn(cmd, args, {
286
+ stdio: ["inherit", "pipe", "pipe"],
287
+ shell: true,
288
+ detached: true, // Create a new process group
289
+ cwd: process.cwd(), // Explicitly set working directory
290
+ env: {
291
+ ...process.env,
292
+ NODE_ENV: "development",
293
+ FORCE_COLOR: "3",
294
+ },
295
+ });
296
+ }
297
+ this.frontendProcess.stdout?.on("data", (data) => {
298
+ const output = data.toString();
299
+ // Debug: Log all frontend output
300
+ if (process.env.DEBUG_DEV_SERVER) {
301
+ logger.info(chalk.blue(`[Frontend stdout] ${output.trim()}`));
302
+ }
303
+ // Detect when frontend is ready
304
+ if (!this.hasDetectedFrontendReady) {
305
+ if (output.includes("ready in") ||
306
+ output.includes("Local:") ||
307
+ output.includes("➜") ||
308
+ output.includes(`${this.config.frontendPort}`)) {
309
+ // Extract actual port if different
310
+ const portMatch = output.match(/localhost:(\d+)/);
311
+ if (portMatch) {
312
+ this.config.frontendPort = parseInt(portMatch[1]);
313
+ }
314
+ this.hasDetectedFrontendReady = true;
315
+ this.isFrontendReady = true;
316
+ this.showStartupBanner();
317
+ return;
318
+ }
319
+ }
320
+ // Check for port conflict
321
+ if (output.includes("EADDRINUSE") ||
322
+ output.includes("already in use") ||
323
+ output.includes("Please stop the other process")) {
324
+ // Store error for the exit handler
325
+ this.frontendError = `Port ${this.config.frontendPort} is already in use`;
326
+ logger.error(chalk.red(`\n⚠️ Port ${this.config.frontendPort} is already in use!`));
327
+ logger.error(chalk.yellow("Please stop the other process or use a different port.\n"));
328
+ this.cleanup();
329
+ process.exit(1);
330
+ }
331
+ // Show errors
332
+ if (output.toLowerCase().includes("error")) {
333
+ const lines = output.trim().split("\n");
334
+ lines.forEach((line) => {
335
+ if (line.trim() && !line.includes("➜")) {
336
+ // Strip existing ANSI codes to prevent corruption
337
+ // eslint-disable-next-line no-control-regex
338
+ const cleanLine = line.replace(/\x1b\[[0-9;]*m/g, "").trim();
339
+ const msg = chalk.red(`[Frontend] ${cleanLine}`);
340
+ if (this.hasShownBanner) {
341
+ logger.error(msg);
342
+ }
343
+ else {
344
+ this.outputBuffer.push(msg);
345
+ }
346
+ }
347
+ });
348
+ }
349
+ });
350
+ this.frontendProcess.stderr?.on("data", (data) => {
351
+ const output = data.toString().trim();
352
+ if (output) {
353
+ // Debug: Log all frontend stderr
354
+ if (process.env.DEBUG_DEV_SERVER) {
355
+ logger.info(chalk.yellow(`[Frontend stderr] ${output}`));
356
+ }
357
+ // Check for Vite's port conflict error message
358
+ // Vite outputs: "Error: Port XXXX is already in use"
359
+ if (output.includes("Error:") && output.includes("is already in use")) {
360
+ // Extract port number from error message if possible
361
+ const portMatch = output.match(/Port (\d+)/);
362
+ const port = portMatch
363
+ ? parseInt(portMatch[1])
364
+ : this.config.frontendPort;
365
+ this.frontendError = `Port ${port} is already in use`;
366
+ // Don't call showPortConflictError immediately as Vite will exit
367
+ // The exit handler will show the proper error banner
368
+ return;
369
+ }
370
+ // Check for other port conflict formats
371
+ if ((output.includes("Port") && output.includes("is in use")) ||
372
+ output.includes("EADDRINUSE")) {
373
+ // Store error for the exit handler
374
+ this.frontendError = `Port ${this.config.frontendPort} is already in use`;
375
+ // Try to show the error immediately, but Vite might exit first
376
+ this.showPortConflictError(this.config.frontendPort, "frontend");
377
+ return;
378
+ }
379
+ // Check if Vite is outputting its ready message to stderr
380
+ if (!this.hasDetectedFrontendReady) {
381
+ if (output.includes("ready in") ||
382
+ output.includes("Local:") ||
383
+ output.includes("➜") ||
384
+ output.includes(`${this.config.frontendPort}`)) {
385
+ // Extract actual port if different
386
+ const portMatch = output.match(/localhost:(\d+)/);
387
+ if (portMatch) {
388
+ this.config.frontendPort = parseInt(portMatch[1]);
389
+ }
390
+ this.hasDetectedFrontendReady = true;
391
+ this.isFrontendReady = true;
392
+ this.showStartupBanner();
393
+ return;
394
+ }
395
+ }
396
+ // For important errors, show them immediately
397
+ if (output.toLowerCase().includes("error") &&
398
+ !output.includes("ExperimentalWarning")) {
399
+ logger.error(chalk.red(`[Frontend] ${output}`));
400
+ }
401
+ else if (!this.hasShownBanner) {
402
+ // Buffer other output until banner is shown
403
+ this.outputBuffer.push(output);
404
+ }
405
+ else {
406
+ // After banner, pass through as-is
407
+ process.stderr.write(output + "\n");
408
+ }
409
+ }
410
+ });
411
+ this.frontendProcess.on("error", (error) => {
412
+ // EAGAIN errors are temporary resource issues, retry
413
+ if (error.code === "EAGAIN" && this.retryCount < 3) {
414
+ this.retryCount++;
415
+ logger.warn(chalk.yellow(`[Frontend] Resource temporarily unavailable, retrying (${this.retryCount}/3)...`));
416
+ setTimeout(async () => await this.startFrontend(), 1000 * this.retryCount);
417
+ return;
418
+ }
419
+ // For other errors, show in yellow (warning) not red (error) since it might still work
420
+ logger.warn(chalk.yellow(`[Frontend] Process spawn warning: ${error.message}`));
421
+ if (process.env.DEBUG_DEV_SERVER) {
422
+ logger.info(chalk.gray(`[Frontend] Command: ${cmd} ${args.join(" ")}`));
423
+ logger.info(chalk.gray(`[Frontend] Directory: ${process.cwd()}`));
424
+ }
425
+ });
426
+ this.frontendProcess.on("exit", (code) => {
427
+ if (code !== 0 && code !== null) {
428
+ // Show specific error if we captured one
429
+ if (this.frontendError) {
430
+ console.clear();
431
+ logger.error(boxen(chalk.red("⚠️ Frontend Failed to Start!\n\n") +
432
+ chalk.white(this.frontendError + "\n\n") +
433
+ chalk.gray("To fix this issue:\n") +
434
+ chalk.gray(`1. Stop the other process using port ${this.config.frontendPort}\n`) +
435
+ chalk.gray(`2. Check what's using it: ${chalk.cyan(`lsof -i :${this.config.frontendPort}`)}\n`) +
436
+ chalk.gray(`3. Kill it with: ${chalk.cyan(`kill -9 <PID>`)}\n`) +
437
+ chalk.gray(`4. Or use a different port in your configuration`), {
438
+ padding: 1,
439
+ margin: 1,
440
+ borderStyle: "round",
441
+ borderColor: "red",
442
+ }));
443
+ }
444
+ else {
445
+ logger.error(chalk.red(`[Frontend] Exited with code ${code}`));
446
+ if (!this.hasDetectedFrontendReady) {
447
+ logger.error(chalk.red(`[Frontend] Failed to start properly. Check that the frontend command works manually.`));
448
+ }
449
+ }
450
+ // Exit the dev-server if frontend fails to start
451
+ if (!this.hasDetectedFrontendReady) {
452
+ this.cleanup();
453
+ process.exit(1);
454
+ }
455
+ }
456
+ });
457
+ // Fallback: Mark as ready after timeout if we haven't detected it
458
+ setTimeout(() => {
459
+ if (!this.hasDetectedFrontendReady) {
460
+ logger.warn(chalk.yellow("Frontend ready detection timeout - assuming ready"));
461
+ this.hasDetectedFrontendReady = true;
462
+ this.isFrontendReady = true;
463
+ this.showStartupBanner();
464
+ }
465
+ }, 5000);
466
+ }
467
+ cleanup() {
468
+ // Kill process groups to ensure all child processes are terminated
469
+ if (this.backendProcess && this.backendProcess.pid) {
470
+ try {
471
+ // Kill the entire process group (negative PID)
472
+ process.kill(-this.backendProcess.pid, "SIGTERM");
473
+ }
474
+ catch (_e) {
475
+ // Fallback to regular kill
476
+ try {
477
+ this.backendProcess.kill("SIGTERM");
478
+ }
479
+ catch (_err) {
480
+ // Process might already be dead
481
+ }
482
+ }
483
+ this.backendProcess = null;
484
+ }
485
+ if (this.frontendProcess && this.frontendProcess.pid) {
486
+ try {
487
+ // Kill the entire process group (negative PID)
488
+ process.kill(-this.frontendProcess.pid, "SIGTERM");
489
+ }
490
+ catch (_e) {
491
+ // Fallback to regular kill
492
+ try {
493
+ this.frontendProcess.kill("SIGTERM");
494
+ }
495
+ catch (_err) {
496
+ // Process might already be dead
497
+ }
498
+ }
499
+ this.frontendProcess = null;
500
+ }
501
+ }
502
+ async checkPort(port) {
503
+ return new Promise((resolve) => {
504
+ const server = net.createServer();
505
+ server.once("error", (err) => {
506
+ if (err.code === "EADDRINUSE") {
507
+ resolve(false);
508
+ }
509
+ else {
510
+ resolve(true);
511
+ }
512
+ });
513
+ server.once("listening", () => {
514
+ server.close();
515
+ resolve(true);
516
+ });
517
+ server.listen(port);
518
+ });
519
+ }
520
+ async waitForPort(port, timeoutMs = 8000, host = "127.0.0.1") {
521
+ const start = Date.now();
522
+ return new Promise((resolve) => {
523
+ const tryConnect = () => {
524
+ const socket = net.connect({ port, host }, () => {
525
+ socket.end();
526
+ resolve(true);
527
+ });
528
+ socket.on("error", () => {
529
+ socket.destroy();
530
+ if (Date.now() - start >= timeoutMs) {
531
+ resolve(false);
532
+ }
533
+ else {
534
+ setTimeout(tryConnect, 200);
535
+ }
536
+ });
537
+ };
538
+ tryConnect();
539
+ });
540
+ }
541
+ async getProcessUsingPort(port) {
542
+ try {
543
+ const { execSync } = await import("child_process");
544
+ // Get PID using the port
545
+ const pidOutput = execSync(`lsof -i :${port} -P -t -sTCP:LISTEN 2>/dev/null || true`, { encoding: "utf-8" });
546
+ const pid = pidOutput.trim().split("\n")[0]; // Get first PID if multiple
547
+ if (!pid)
548
+ return null;
549
+ // Get full process command
550
+ const commandOutput = execSync(`ps -p ${pid} -o command= 2>/dev/null || echo "unknown"`, { encoding: "utf-8" }).trim();
551
+ // Simplify the command for display
552
+ let command = commandOutput;
553
+ if (command.includes("node")) {
554
+ if (command.includes("tsx"))
555
+ command = "tsx (TypeScript)";
556
+ else if (command.includes("vite"))
557
+ command = "vite (dev server)";
558
+ else if (command.includes("nodemon"))
559
+ command = "nodemon";
560
+ else
561
+ command = "node process";
562
+ }
563
+ return { pid, command };
564
+ }
565
+ catch {
566
+ return null;
567
+ }
568
+ }
569
+ async showPortConflictError(port, portType = "backend") {
570
+ const processInfo = await this.getProcessUsingPort(port);
571
+ console.clear();
572
+ // Build the error message with subtle gray styling like the startup banner
573
+ let lines = [];
574
+ lines.push("");
575
+ lines.push(`⚠️ ${chalk.yellow(portType.charAt(0).toUpperCase() + portType.slice(1))} port ${chalk.yellow(port)} is already in use!`);
576
+ lines.push("");
577
+ if (processInfo) {
578
+ lines.push(chalk.gray(`Process: ${chalk.yellow(processInfo.command)}`));
579
+ lines.push(chalk.gray(`PID: ${chalk.yellow(processInfo.pid)}`));
580
+ lines.push("");
581
+ lines.push(chalk.gray("🔧 To fix this, run:"));
582
+ lines.push("");
583
+ lines.push(chalk.cyan(` kill -9 ${processInfo.pid}`));
584
+ lines.push("");
585
+ }
586
+ else {
587
+ lines.push(chalk.gray("Could not identify the process using this port."));
588
+ lines.push("");
589
+ }
590
+ lines.push(chalk.gray("Other options:"));
591
+ lines.push(chalk.gray(`• Check what's using it: ${chalk.cyan(`lsof -i :${port}`)}`));
592
+ lines.push(chalk.gray(`• Kill all Node processes: ${chalk.cyan("pkill -f node")}`));
593
+ lines.push(chalk.gray(`• Use a different port in your package.json`));
594
+ lines.push("");
595
+ // Use subtle gray box like the startup banner
596
+ logger.error(boxen(lines.join("\n"), {
597
+ padding: 1,
598
+ margin: 1,
599
+ borderStyle: "round",
600
+ borderColor: "gray",
601
+ dimBorder: true,
602
+ }));
603
+ this.cleanup();
604
+ process.exit(1);
605
+ }
606
+ async start() {
607
+ logger.info(chalk.cyan(`\nStarting ${this.config.appName} development server...\n`));
608
+ // Check if backend port is available
609
+ const backendAvailable = await this.checkPort(this.config.backendPort);
610
+ if (!backendAvailable) {
611
+ await this.showPortConflictError(this.config.backendPort, "backend");
612
+ return;
613
+ }
614
+ // Check if frontend port is available
615
+ const frontendAvailable = await this.checkPort(this.config.frontendPort);
616
+ if (!frontendAvailable) {
617
+ await this.showPortConflictError(this.config.frontendPort, "frontend");
618
+ return;
619
+ }
620
+ // Start both processes
621
+ this.startBackend();
622
+ // Give backend a moment to start before frontend
623
+ setTimeout(async () => await this.startFrontend(), 1000);
624
+ // Handle graceful shutdown
625
+ process.on("SIGINT", () => {
626
+ logger.info(chalk.yellow("\n\nShutting down..."));
627
+ this.cleanup();
628
+ // Force exit after a delay if processes don't terminate
629
+ setTimeout(() => process.exit(0), 1000);
630
+ });
631
+ process.on("SIGTERM", () => {
632
+ this.cleanup();
633
+ process.exit(0);
634
+ });
635
+ }
636
+ }
637
+ // Run the orchestrator
638
+ const orchestrator = new DevServerOrchestrator();
639
+ orchestrator.start();
640
+ //# sourceMappingURL=dev-server.js.map