@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.
- package/LICENSE +21 -0
- package/README.md +652 -0
- package/dist/api/logsRouter.d.ts +20 -0
- package/dist/api/logsRouter.d.ts.map +1 -0
- package/dist/api/logsRouter.js +515 -0
- package/dist/api/logsRouter.js.map +1 -0
- package/dist/cli/dev-server.d.ts +7 -0
- package/dist/cli/dev-server.d.ts.map +1 -0
- package/dist/cli/dev-server.js +640 -0
- package/dist/cli/dev-server.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +26 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/StandardServer.d.ts +129 -0
- package/dist/core/StandardServer.d.ts.map +1 -0
- package/dist/core/StandardServer.js +453 -0
- package/dist/core/StandardServer.js.map +1 -0
- package/dist/core/apiResponse.d.ts +69 -0
- package/dist/core/apiResponse.d.ts.map +1 -0
- package/dist/core/apiResponse.js +127 -0
- package/dist/core/apiResponse.js.map +1 -0
- package/dist/core/healthCheck.d.ts +160 -0
- package/dist/core/healthCheck.d.ts.map +1 -0
- package/dist/core/healthCheck.js +398 -0
- package/dist/core/healthCheck.js.map +1 -0
- package/dist/core/index.d.ts +40 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +40 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +117 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +826 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/portUtils.d.ts +71 -0
- package/dist/core/portUtils.d.ts.map +1 -0
- package/dist/core/portUtils.js +240 -0
- package/dist/core/portUtils.js.map +1 -0
- package/dist/core/storageService.d.ts +119 -0
- package/dist/core/storageService.d.ts.map +1 -0
- package/dist/core/storageService.js +405 -0
- package/dist/core/storageService.js.map +1 -0
- package/dist/desktop/bundler.d.ts +40 -0
- package/dist/desktop/bundler.d.ts.map +1 -0
- package/dist/desktop/bundler.js +176 -0
- package/dist/desktop/bundler.js.map +1 -0
- package/dist/desktop/index.d.ts +25 -0
- package/dist/desktop/index.d.ts.map +1 -0
- package/dist/desktop/index.js +15 -0
- package/dist/desktop/index.js.map +1 -0
- package/dist/desktop/native-modules.d.ts +66 -0
- package/dist/desktop/native-modules.d.ts.map +1 -0
- package/dist/desktop/native-modules.js +200 -0
- package/dist/desktop/native-modules.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/LogCategories.d.ts +87 -0
- package/dist/logging/LogCategories.d.ts.map +1 -0
- package/dist/logging/LogCategories.js +205 -0
- package/dist/logging/LogCategories.js.map +1 -0
- package/dist/middleware/aiErrorHandler.d.ts +31 -0
- package/dist/middleware/aiErrorHandler.d.ts.map +1 -0
- package/dist/middleware/aiErrorHandler.js +181 -0
- package/dist/middleware/aiErrorHandler.js.map +1 -0
- package/dist/middleware/auth.d.ts +101 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +230 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/cors.d.ts +56 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +123 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/errorHandler.d.ts +13 -0
- package/dist/middleware/errorHandler.d.ts.map +1 -0
- package/dist/middleware/errorHandler.js +85 -0
- package/dist/middleware/errorHandler.js.map +1 -0
- package/dist/middleware/fileUpload.d.ts +62 -0
- package/dist/middleware/fileUpload.d.ts.map +1 -0
- package/dist/middleware/fileUpload.js +175 -0
- package/dist/middleware/fileUpload.js.map +1 -0
- package/dist/middleware/health.d.ts +48 -0
- package/dist/middleware/health.d.ts.map +1 -0
- package/dist/middleware/health.js +143 -0
- package/dist/middleware/health.js.map +1 -0
- package/dist/middleware/index.d.ts +20 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +18 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/openapi.d.ts +64 -0
- package/dist/middleware/openapi.d.ts.map +1 -0
- package/dist/middleware/openapi.js +258 -0
- package/dist/middleware/openapi.js.map +1 -0
- package/dist/middleware/requestLogging.d.ts +22 -0
- package/dist/middleware/requestLogging.d.ts.map +1 -0
- package/dist/middleware/requestLogging.js +61 -0
- package/dist/middleware/requestLogging.js.map +1 -0
- package/dist/middleware/session.d.ts +84 -0
- package/dist/middleware/session.d.ts.map +1 -0
- package/dist/middleware/session.js +189 -0
- package/dist/middleware/session.js.map +1 -0
- package/dist/middleware/validation.d.ts +1337 -0
- package/dist/middleware/validation.d.ts.map +1 -0
- package/dist/middleware/validation.js +483 -0
- package/dist/middleware/validation.js.map +1 -0
- package/dist/services/aiService.d.ts +180 -0
- package/dist/services/aiService.d.ts.map +1 -0
- package/dist/services/aiService.js +547 -0
- package/dist/services/aiService.js.map +1 -0
- package/dist/services/conversationStorage.d.ts +38 -0
- package/dist/services/conversationStorage.d.ts.map +1 -0
- package/dist/services/conversationStorage.js +158 -0
- package/dist/services/conversationStorage.js.map +1 -0
- package/dist/services/crossPlatformBuffer.d.ts +84 -0
- package/dist/services/crossPlatformBuffer.d.ts.map +1 -0
- package/dist/services/crossPlatformBuffer.js +246 -0
- package/dist/services/crossPlatformBuffer.js.map +1 -0
- package/dist/services/index.d.ts +17 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +18 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/networkService.d.ts +81 -0
- package/dist/services/networkService.d.ts.map +1 -0
- package/dist/services/networkService.js +268 -0
- package/dist/services/networkService.js.map +1 -0
- package/dist/services/queueService.d.ts +112 -0
- package/dist/services/queueService.d.ts.map +1 -0
- package/dist/services/queueService.js +338 -0
- package/dist/services/queueService.js.map +1 -0
- package/dist/services/settingsService.d.ts +135 -0
- package/dist/services/settingsService.d.ts.map +1 -0
- package/dist/services/settingsService.js +425 -0
- package/dist/services/settingsService.js.map +1 -0
- package/dist/services/systemMonitor.d.ts +208 -0
- package/dist/services/systemMonitor.d.ts.map +1 -0
- package/dist/services/systemMonitor.js +693 -0
- package/dist/services/systemMonitor.js.map +1 -0
- package/dist/services/updateService.d.ts +78 -0
- package/dist/services/updateService.d.ts.map +1 -0
- package/dist/services/updateService.js +252 -0
- package/dist/services/updateService.js.map +1 -0
- package/dist/services/websocketEvents.d.ts +372 -0
- package/dist/services/websocketEvents.d.ts.map +1 -0
- package/dist/services/websocketEvents.js +338 -0
- package/dist/services/websocketEvents.js.map +1 -0
- package/dist/services/websocketServer.d.ts +80 -0
- package/dist/services/websocketServer.d.ts.map +1 -0
- package/dist/services/websocketServer.js +299 -0
- package/dist/services/websocketServer.js.map +1 -0
- package/dist/settings/SettingsSchema.d.ts +151 -0
- package/dist/settings/SettingsSchema.d.ts.map +1 -0
- package/dist/settings/SettingsSchema.js +424 -0
- package/dist/settings/SettingsSchema.js.map +1 -0
- package/dist/testing/TestServer.d.ts +69 -0
- package/dist/testing/TestServer.d.ts.map +1 -0
- package/dist/testing/TestServer.js +250 -0
- package/dist/testing/TestServer.js.map +1 -0
- package/dist/types/index.d.ts +137 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/appPaths.d.ts +74 -0
- package/dist/utils/appPaths.d.ts.map +1 -0
- package/dist/utils/appPaths.js +162 -0
- package/dist/utils/appPaths.js.map +1 -0
- package/dist/utils/fs-utils.d.ts +50 -0
- package/dist/utils/fs-utils.d.ts.map +1 -0
- package/dist/utils/fs-utils.js +114 -0
- package/dist/utils/fs-utils.js.map +1 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/standardConfig.d.ts +61 -0
- package/dist/utils/standardConfig.d.ts.map +1 -0
- package/dist/utils/standardConfig.js +109 -0
- package/dist/utils/standardConfig.js.map +1 -0
- package/dist/utils/startupBanner.d.ts +34 -0
- package/dist/utils/startupBanner.d.ts.map +1 -0
- package/dist/utils/startupBanner.js +169 -0
- package/dist/utils/startupBanner.js.map +1 -0
- package/dist/utils/startupLogger.d.ts +45 -0
- package/dist/utils/startupLogger.d.ts.map +1 -0
- package/dist/utils/startupLogger.js +200 -0
- package/dist/utils/startupLogger.js.map +1 -0
- package/package.json +151 -0
- package/src/api/logsRouter.ts +600 -0
- package/src/cli/dev-server.ts +803 -0
- package/src/cli/index.ts +31 -0
- package/src/core/StandardServer.ts +587 -0
- package/src/core/apiResponse.ts +202 -0
- package/src/core/healthCheck.ts +565 -0
- package/src/core/index.ts +80 -0
- package/src/core/logger.ts +1092 -0
- package/src/core/portUtils.ts +319 -0
- package/src/core/storageService.ts +595 -0
- package/src/desktop/bundler.ts +271 -0
- package/src/desktop/index.ts +18 -0
- package/src/desktop/native-modules.ts +289 -0
- package/src/index.ts +142 -0
- package/src/logging/LogCategories.ts +302 -0
- package/src/middleware/aiErrorHandler.ts +278 -0
- package/src/middleware/auth.ts +329 -0
- package/src/middleware/cors.ts +187 -0
- package/src/middleware/errorHandler.ts +103 -0
- package/src/middleware/fileUpload.ts +252 -0
- package/src/middleware/health.ts +206 -0
- package/src/middleware/index.ts +71 -0
- package/src/middleware/openapi.ts +305 -0
- package/src/middleware/requestLogging.ts +92 -0
- package/src/middleware/session.ts +238 -0
- package/src/middleware/validation.ts +603 -0
- package/src/services/aiService.ts +789 -0
- package/src/services/conversationStorage.ts +232 -0
- package/src/services/crossPlatformBuffer.ts +341 -0
- package/src/services/index.ts +47 -0
- package/src/services/networkService.ts +351 -0
- package/src/services/queueService.ts +446 -0
- package/src/services/settingsService.ts +549 -0
- package/src/services/systemMonitor.ts +936 -0
- package/src/services/updateService.ts +334 -0
- package/src/services/websocketEvents.ts +409 -0
- package/src/services/websocketServer.ts +394 -0
- package/src/settings/SettingsSchema.ts +664 -0
- package/src/testing/TestServer.ts +312 -0
- package/src/types/index.ts +154 -0
- package/src/utils/appPaths.ts +196 -0
- package/src/utils/fs-utils.ts +130 -0
- package/src/utils/index.ts +15 -0
- package/src/utils/standardConfig.ts +178 -0
- package/src/utils/startupBanner.ts +287 -0
- package/src/utils/startupLogger.ts +268 -0
- package/ui/dist/index.d.mts +1221 -0
- package/ui/dist/index.d.ts +1221 -0
- package/ui/dist/index.js +73 -0
- package/ui/dist/index.js.map +1 -0
- package/ui/dist/index.mjs +73 -0
- package/ui/dist/index.mjs.map +1 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Server Utility
|
|
3
|
+
* Provides standardized test server management for all SuperDangerous applications
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawn, ChildProcess } from "child_process";
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
import { createLogger } from "../core/index.js";
|
|
9
|
+
|
|
10
|
+
let logger: any; // Will be initialized when needed
|
|
11
|
+
|
|
12
|
+
function ensureLogger() {
|
|
13
|
+
if (!logger) {
|
|
14
|
+
logger = createLogger("TestServer");
|
|
15
|
+
}
|
|
16
|
+
return logger;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface TestServerConfig {
|
|
20
|
+
/** Path to the application entry point */
|
|
21
|
+
entryPoint: string;
|
|
22
|
+
/** Port to run the server on */
|
|
23
|
+
port: number;
|
|
24
|
+
/** API base URL */
|
|
25
|
+
apiBase?: string;
|
|
26
|
+
/** Environment variables */
|
|
27
|
+
env?: Record<string, string>;
|
|
28
|
+
/** Timeout for server startup (ms) */
|
|
29
|
+
startupTimeout?: number;
|
|
30
|
+
/** Health check endpoint */
|
|
31
|
+
healthEndpoint?: string;
|
|
32
|
+
/** Whether to suppress server output */
|
|
33
|
+
silent?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class TestServer {
|
|
37
|
+
private process: ChildProcess | null = null;
|
|
38
|
+
private config: Required<TestServerConfig>;
|
|
39
|
+
private startPromise: Promise<void> | null = null;
|
|
40
|
+
public port: number;
|
|
41
|
+
|
|
42
|
+
constructor(config: TestServerConfig) {
|
|
43
|
+
this.port = config.port;
|
|
44
|
+
this.config = {
|
|
45
|
+
apiBase: `http://localhost:${config.port}`,
|
|
46
|
+
env: {},
|
|
47
|
+
startupTimeout: 30000,
|
|
48
|
+
healthEndpoint: "/api/health",
|
|
49
|
+
silent: true,
|
|
50
|
+
...config,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Clean up any existing processes on the port
|
|
56
|
+
*/
|
|
57
|
+
private cleanupPort(): void {
|
|
58
|
+
try {
|
|
59
|
+
if (process.platform === "win32") {
|
|
60
|
+
// Windows: Find and kill process using the port
|
|
61
|
+
execSync(
|
|
62
|
+
`netstat -ano | findstr :${this.config.port} | findstr LISTENING`,
|
|
63
|
+
{ stdio: "ignore" },
|
|
64
|
+
);
|
|
65
|
+
const result = execSync(
|
|
66
|
+
`netstat -ano | findstr :${this.config.port} | findstr LISTENING`,
|
|
67
|
+
).toString();
|
|
68
|
+
const lines = result.split("\n");
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
const parts = line.trim().split(/\s+/);
|
|
71
|
+
const pid = parts[parts.length - 1];
|
|
72
|
+
if (pid && pid !== "0") {
|
|
73
|
+
execSync(`taskkill /F /PID ${pid}`, { stdio: "ignore" });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
// Unix-like: Use lsof to find and kill process
|
|
78
|
+
execSync(
|
|
79
|
+
`lsof -ti:${this.config.port} | xargs kill -9 2>/dev/null || true`,
|
|
80
|
+
{ stdio: "ignore" },
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
} catch (_error) {
|
|
84
|
+
// Port might already be free, ignore errors
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Wait for the server to be ready
|
|
90
|
+
*/
|
|
91
|
+
private async waitForServer(): Promise<void> {
|
|
92
|
+
const startTime = Date.now();
|
|
93
|
+
const { apiBase, healthEndpoint, startupTimeout } = this.config;
|
|
94
|
+
const healthUrl = `${apiBase}${healthEndpoint}`;
|
|
95
|
+
|
|
96
|
+
if (!this.config.silent) {
|
|
97
|
+
console.log(`Waiting for server at ${healthUrl}...`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
while (Date.now() - startTime < startupTimeout) {
|
|
101
|
+
try {
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
const timeoutId = setTimeout(() => controller.abort(), 1000);
|
|
104
|
+
|
|
105
|
+
const response = await fetch(healthUrl, {
|
|
106
|
+
signal: controller.signal,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
clearTimeout(timeoutId);
|
|
110
|
+
|
|
111
|
+
if (response.ok) {
|
|
112
|
+
if (!this.config.silent) {
|
|
113
|
+
console.log(`Server is ready at ${healthUrl}`);
|
|
114
|
+
}
|
|
115
|
+
return; // Server is ready
|
|
116
|
+
}
|
|
117
|
+
} catch (_error) {
|
|
118
|
+
// Server not ready yet
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check if process has exited
|
|
122
|
+
if (this.process && this.process.exitCode !== null) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`Server process exited with code ${this.process.exitCode}`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
throw new Error(`Server failed to start within ${startupTimeout}ms`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Start the test server
|
|
136
|
+
*/
|
|
137
|
+
async start(): Promise<void> {
|
|
138
|
+
// Return existing promise if already starting
|
|
139
|
+
if (this.startPromise) {
|
|
140
|
+
return this.startPromise;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Already running
|
|
144
|
+
if (this.process) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.startPromise = this._start();
|
|
149
|
+
return this.startPromise;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private async _start(): Promise<void> {
|
|
153
|
+
// Clean up any existing processes
|
|
154
|
+
this.cleanupPort();
|
|
155
|
+
|
|
156
|
+
// Determine command based on entry point extension
|
|
157
|
+
const { entryPoint } = this.config;
|
|
158
|
+
const isTypeScript = entryPoint.endsWith(".ts");
|
|
159
|
+
const command = isTypeScript ? "npx" : "node";
|
|
160
|
+
const args = isTypeScript ? ["tsx", entryPoint] : [entryPoint];
|
|
161
|
+
|
|
162
|
+
// Start the server process
|
|
163
|
+
this.process = spawn(command, args, {
|
|
164
|
+
env: {
|
|
165
|
+
...process.env,
|
|
166
|
+
...this.config.env,
|
|
167
|
+
NODE_ENV: "test",
|
|
168
|
+
PORT: String(this.config.port),
|
|
169
|
+
API_PORT: String(this.config.port),
|
|
170
|
+
SILENT_STARTUP: "1",
|
|
171
|
+
},
|
|
172
|
+
stdio: this.config.silent ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Handle process output if not silent
|
|
176
|
+
if (this.config.silent && this.process.stdout && this.process.stderr) {
|
|
177
|
+
this.process.stdout.on("data", (data) => {
|
|
178
|
+
const output = data.toString();
|
|
179
|
+
// Only log errors or important messages
|
|
180
|
+
if (output.includes("ERROR") || output.includes("WARN")) {
|
|
181
|
+
ensureLogger().debug(`Server output: ${output}`);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
this.process.stderr.on("data", (data) => {
|
|
186
|
+
ensureLogger().error(`Server error: ${data}`);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Handle process exit
|
|
191
|
+
this.process.on("exit", (code) => {
|
|
192
|
+
if (code !== 0 && code !== null) {
|
|
193
|
+
ensureLogger().error(`Server process exited with code ${code}`);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Wait for server to be ready
|
|
198
|
+
await this.waitForServer();
|
|
199
|
+
ensureLogger().debug(`Test server started on port ${this.config.port}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Stop the test server
|
|
204
|
+
*/
|
|
205
|
+
async stop(): Promise<void> {
|
|
206
|
+
if (!this.process) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return new Promise((resolve) => {
|
|
211
|
+
if (!this.process) {
|
|
212
|
+
resolve();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const cleanup = () => {
|
|
217
|
+
this.process = null;
|
|
218
|
+
this.startPromise = null;
|
|
219
|
+
resolve();
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Set a timeout for graceful shutdown
|
|
223
|
+
const killTimeout = setTimeout(() => {
|
|
224
|
+
if (this.process) {
|
|
225
|
+
this.process.kill("SIGKILL");
|
|
226
|
+
}
|
|
227
|
+
cleanup();
|
|
228
|
+
}, 5000);
|
|
229
|
+
|
|
230
|
+
this.process.on("exit", () => {
|
|
231
|
+
clearTimeout(killTimeout);
|
|
232
|
+
cleanup();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Try graceful shutdown first
|
|
236
|
+
this.process.kill("SIGTERM");
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get the API base URL
|
|
242
|
+
*/
|
|
243
|
+
getApiBase(): string {
|
|
244
|
+
return this.config.apiBase;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Make a request to the test server
|
|
249
|
+
*/
|
|
250
|
+
async request(endpoint: string, options: any = {}): Promise<any> {
|
|
251
|
+
const url = `${this.config.apiBase}${endpoint}`;
|
|
252
|
+
const response = await fetch(url, {
|
|
253
|
+
...options,
|
|
254
|
+
headers: {
|
|
255
|
+
"Content-Type": "application/json",
|
|
256
|
+
...options.headers,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const text = await response.text();
|
|
261
|
+
let data;
|
|
262
|
+
try {
|
|
263
|
+
data = JSON.parse(text);
|
|
264
|
+
} catch {
|
|
265
|
+
data = text;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
status: response.status,
|
|
270
|
+
ok: response.ok,
|
|
271
|
+
data,
|
|
272
|
+
response,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Create a test server instance
|
|
279
|
+
*/
|
|
280
|
+
export function createTestServer(config: TestServerConfig): TestServer {
|
|
281
|
+
return new TestServer(config);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Global test server instance for Jest
|
|
286
|
+
*/
|
|
287
|
+
let globalTestServer: TestServer | null = null;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Setup function for Jest beforeAll
|
|
291
|
+
*/
|
|
292
|
+
export async function setupTestServer(config: TestServerConfig): Promise<void> {
|
|
293
|
+
globalTestServer = new TestServer(config);
|
|
294
|
+
await globalTestServer.start();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Teardown function for Jest afterAll
|
|
299
|
+
*/
|
|
300
|
+
export async function teardownTestServer(): Promise<void> {
|
|
301
|
+
if (globalTestServer) {
|
|
302
|
+
await globalTestServer.stop();
|
|
303
|
+
globalTestServer = null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Get the global test server instance
|
|
309
|
+
*/
|
|
310
|
+
export function getTestServer(): TestServer | null {
|
|
311
|
+
return globalTestServer;
|
|
312
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared TypeScript Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Field validation error detail
|
|
7
|
+
*/
|
|
8
|
+
export interface FieldValidationError {
|
|
9
|
+
field: string;
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Standardized API response structure for all applications
|
|
15
|
+
* Supports both legacy string errors and new structured errors
|
|
16
|
+
* @template T - The type of the data payload
|
|
17
|
+
*/
|
|
18
|
+
export interface ApiResponse<T = any> {
|
|
19
|
+
success: boolean;
|
|
20
|
+
data?: T;
|
|
21
|
+
error?:
|
|
22
|
+
| string
|
|
23
|
+
| {
|
|
24
|
+
code: string;
|
|
25
|
+
message: string;
|
|
26
|
+
details?: any;
|
|
27
|
+
stack?: string; // Only in development
|
|
28
|
+
};
|
|
29
|
+
message?: string; // Optional success message
|
|
30
|
+
errors?: FieldValidationError[]; // For validation errors
|
|
31
|
+
metadata?: {
|
|
32
|
+
timestamp: string;
|
|
33
|
+
version?: string;
|
|
34
|
+
requestId?: string;
|
|
35
|
+
pagination?: {
|
|
36
|
+
page: number;
|
|
37
|
+
limit: number;
|
|
38
|
+
total: number;
|
|
39
|
+
hasMore: boolean;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
timestamp?: string; // Legacy field for backward compatibility
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Legacy simple error format (deprecated - for backward compatibility)
|
|
47
|
+
* @deprecated Use structured error object instead
|
|
48
|
+
*/
|
|
49
|
+
export interface ApiResponseLegacy<T = any> {
|
|
50
|
+
success: boolean;
|
|
51
|
+
data?: T;
|
|
52
|
+
error?: string;
|
|
53
|
+
message?: string;
|
|
54
|
+
timestamp?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Configuration options
|
|
59
|
+
*/
|
|
60
|
+
export interface ConfigOptions {
|
|
61
|
+
configPath?: string;
|
|
62
|
+
defaultConfig?: any;
|
|
63
|
+
watchChanges?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Network interface information
|
|
68
|
+
*/
|
|
69
|
+
export interface NetworkInterface {
|
|
70
|
+
name: string;
|
|
71
|
+
address: string;
|
|
72
|
+
family: "IPv4" | "IPv6";
|
|
73
|
+
internal: boolean;
|
|
74
|
+
mac?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Update check result
|
|
79
|
+
*/
|
|
80
|
+
export interface UpdateCheckResult {
|
|
81
|
+
hasUpdate: boolean;
|
|
82
|
+
currentVersion: string;
|
|
83
|
+
latestVersion?: string;
|
|
84
|
+
releaseUrl?: string;
|
|
85
|
+
releaseNotes?: string;
|
|
86
|
+
publishedAt?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* WebSocket client information
|
|
91
|
+
*/
|
|
92
|
+
export interface ClientInfo {
|
|
93
|
+
id: string;
|
|
94
|
+
connectedAt: Date;
|
|
95
|
+
lastActivity: Date;
|
|
96
|
+
subscriptions: Set<string>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Logger interface
|
|
101
|
+
*/
|
|
102
|
+
export interface Logger {
|
|
103
|
+
info: (...args: any[]) => void;
|
|
104
|
+
error: (...args: any[]) => void;
|
|
105
|
+
warn: (...args: any[]) => void;
|
|
106
|
+
debug: (...args: any[]) => void;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* WebSocket message types
|
|
111
|
+
*/
|
|
112
|
+
export interface WebSocketMessage {
|
|
113
|
+
type: string;
|
|
114
|
+
data: any;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface SimulatorUpdateMessage extends WebSocketMessage {
|
|
118
|
+
type: "simulator:started" | "simulator:stopped" | "simulator:data";
|
|
119
|
+
data: Simulator;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface TemplateUpdateMessage extends WebSocketMessage {
|
|
123
|
+
type: "template:created" | "template:updated" | "template:deleted";
|
|
124
|
+
data: Template;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface DataUpdateMessage extends WebSocketMessage {
|
|
128
|
+
type: "data:update";
|
|
129
|
+
data: {
|
|
130
|
+
simulatorId: string;
|
|
131
|
+
values: Record<string, any>;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface Simulator {
|
|
136
|
+
id: string;
|
|
137
|
+
name: string;
|
|
138
|
+
status: string;
|
|
139
|
+
[key: string]: any;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface Template {
|
|
143
|
+
id: string;
|
|
144
|
+
name: string;
|
|
145
|
+
[key: string]: any;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface AppConfig {
|
|
149
|
+
[key: string]: any;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface ValidationError extends Error {
|
|
153
|
+
details?: any[];
|
|
154
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import os from "os";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { mkdirSync } from "fs";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get the appropriate data directory for the application.
|
|
7
|
+
*
|
|
8
|
+
* In desktop app mode (Electron):
|
|
9
|
+
* - macOS: ~/Library/Application Support/{appId}
|
|
10
|
+
* - Windows: %APPDATA%/{appName}
|
|
11
|
+
* - Linux: ~/.local/share/{appName}
|
|
12
|
+
*
|
|
13
|
+
* In development or web mode:
|
|
14
|
+
* - Uses project root ./data directory
|
|
15
|
+
*
|
|
16
|
+
* @param appId - The application identifier (e.g., 'com.superdangerous.app-name')
|
|
17
|
+
* @param appName - The application name (e.g., 'app-name')
|
|
18
|
+
* @returns The absolute path to the application data directory
|
|
19
|
+
*/
|
|
20
|
+
export function getAppDataPath(appId: string, appName: string): string {
|
|
21
|
+
// Check if running in a desktop app environment
|
|
22
|
+
const isElectron =
|
|
23
|
+
process.versions?.electron || process.env.ELECTRON_RUNNING === "true";
|
|
24
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
25
|
+
|
|
26
|
+
// In development mode, always use local paths unless explicitly forced
|
|
27
|
+
const isDesktopApp = !isDevelopment && isElectron;
|
|
28
|
+
|
|
29
|
+
if (isDesktopApp) {
|
|
30
|
+
// Use platform-specific app data directories
|
|
31
|
+
const platform = process.platform;
|
|
32
|
+
const homeDir = os.homedir();
|
|
33
|
+
let appDataDir: string;
|
|
34
|
+
|
|
35
|
+
switch (platform) {
|
|
36
|
+
case "darwin": // macOS
|
|
37
|
+
appDataDir = path.join(
|
|
38
|
+
homeDir,
|
|
39
|
+
"Library",
|
|
40
|
+
"Application Support",
|
|
41
|
+
appId,
|
|
42
|
+
);
|
|
43
|
+
break;
|
|
44
|
+
case "win32": // Windows
|
|
45
|
+
appDataDir = path.join(
|
|
46
|
+
process.env.APPDATA || path.join(homeDir, "AppData", "Roaming"),
|
|
47
|
+
appName,
|
|
48
|
+
);
|
|
49
|
+
break;
|
|
50
|
+
default: // Linux and others
|
|
51
|
+
appDataDir = path.join(
|
|
52
|
+
process.env.XDG_DATA_HOME || path.join(homeDir, ".local", "share"),
|
|
53
|
+
appName,
|
|
54
|
+
);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Ensure the directory exists
|
|
59
|
+
mkdirSync(appDataDir, { recursive: true });
|
|
60
|
+
return appDataDir;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// In development or web mode, use the project root ./data directory
|
|
64
|
+
const dataDir = path.join(process.cwd(), "data");
|
|
65
|
+
mkdirSync(dataDir, { recursive: true });
|
|
66
|
+
return dataDir;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the full path for a data file within the application data directory.
|
|
71
|
+
*
|
|
72
|
+
* @param filename - The name of the file
|
|
73
|
+
* @param appId - The application identifier
|
|
74
|
+
* @param appName - The application name
|
|
75
|
+
* @returns The absolute path to the file
|
|
76
|
+
*/
|
|
77
|
+
export function getDataFilePath(
|
|
78
|
+
filename: string,
|
|
79
|
+
appId: string,
|
|
80
|
+
appName: string,
|
|
81
|
+
): string {
|
|
82
|
+
return path.join(getAppDataPath(appId, appName), filename);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the logs directory path for the application.
|
|
87
|
+
*
|
|
88
|
+
* @param appId - The application identifier
|
|
89
|
+
* @param appName - The application name
|
|
90
|
+
* @returns The absolute path to the logs directory
|
|
91
|
+
*/
|
|
92
|
+
export function getLogsPath(appId: string, appName: string): string {
|
|
93
|
+
const logsDir = path.join(getAppDataPath(appId, appName), "logs");
|
|
94
|
+
mkdirSync(logsDir, { recursive: true });
|
|
95
|
+
return logsDir;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get the configuration directory path for the application.
|
|
100
|
+
*
|
|
101
|
+
* @param appId - The application identifier
|
|
102
|
+
* @param appName - The application name
|
|
103
|
+
* @returns The absolute path to the config directory
|
|
104
|
+
*/
|
|
105
|
+
export function getConfigPath(appId: string, appName: string): string {
|
|
106
|
+
const configDir = path.join(getAppDataPath(appId, appName), "config");
|
|
107
|
+
mkdirSync(configDir, { recursive: true });
|
|
108
|
+
return configDir;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get the cache directory path for the application.
|
|
113
|
+
*
|
|
114
|
+
* @param appId - The application identifier
|
|
115
|
+
* @param appName - The application name
|
|
116
|
+
* @returns The absolute path to the cache directory
|
|
117
|
+
*/
|
|
118
|
+
export function getCachePath(appId: string, appName: string): string {
|
|
119
|
+
const cacheDir = path.join(getAppDataPath(appId, appName), "cache");
|
|
120
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
121
|
+
return cacheDir;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Check if the application is running in desktop mode (Electron).
|
|
126
|
+
*
|
|
127
|
+
* @returns True if running as a desktop app, false otherwise
|
|
128
|
+
*/
|
|
129
|
+
export function isDesktopApp(): boolean {
|
|
130
|
+
const isDevelopment = process.env.NODE_ENV === "development";
|
|
131
|
+
const isElectron =
|
|
132
|
+
!!process.versions?.electron || process.env.ELECTRON_RUNNING === "true";
|
|
133
|
+
|
|
134
|
+
// In development mode, consider it a desktop app only if explicitly set
|
|
135
|
+
return !isDevelopment && isElectron;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get platform-specific user documents directory.
|
|
140
|
+
*
|
|
141
|
+
* @returns The path to the user's documents directory
|
|
142
|
+
*/
|
|
143
|
+
export function getDocumentsPath(): string {
|
|
144
|
+
const platform = process.platform;
|
|
145
|
+
const homeDir = os.homedir();
|
|
146
|
+
|
|
147
|
+
switch (platform) {
|
|
148
|
+
case "darwin": // macOS
|
|
149
|
+
case "linux":
|
|
150
|
+
return path.join(homeDir, "Documents");
|
|
151
|
+
case "win32": // Windows
|
|
152
|
+
return path.join(homeDir, "Documents");
|
|
153
|
+
default:
|
|
154
|
+
return homeDir;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get platform-specific user desktop directory.
|
|
160
|
+
*
|
|
161
|
+
* @returns The path to the user's desktop directory
|
|
162
|
+
*/
|
|
163
|
+
export function getDesktopPath(): string {
|
|
164
|
+
const platform = process.platform;
|
|
165
|
+
const homeDir = os.homedir();
|
|
166
|
+
|
|
167
|
+
switch (platform) {
|
|
168
|
+
case "darwin": // macOS
|
|
169
|
+
case "linux":
|
|
170
|
+
return path.join(homeDir, "Desktop");
|
|
171
|
+
case "win32": // Windows
|
|
172
|
+
return path.join(homeDir, "Desktop");
|
|
173
|
+
default:
|
|
174
|
+
return homeDir;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get platform-specific user downloads directory.
|
|
180
|
+
*
|
|
181
|
+
* @returns The path to the user's downloads directory
|
|
182
|
+
*/
|
|
183
|
+
export function getDownloadsPath(): string {
|
|
184
|
+
const platform = process.platform;
|
|
185
|
+
const homeDir = os.homedir();
|
|
186
|
+
|
|
187
|
+
switch (platform) {
|
|
188
|
+
case "darwin": // macOS
|
|
189
|
+
case "linux":
|
|
190
|
+
return path.join(homeDir, "Downloads");
|
|
191
|
+
case "win32": // Windows
|
|
192
|
+
return path.join(homeDir, "Downloads");
|
|
193
|
+
default:
|
|
194
|
+
return homeDir;
|
|
195
|
+
}
|
|
196
|
+
}
|