-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstart-pointer.js
More file actions
460 lines (395 loc) · 16.2 KB
/
Copy pathstart-pointer.js
File metadata and controls
460 lines (395 loc) · 16.2 KB
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
const { spawn, exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
const tcpPortUsed = require('tcp-port-used');
const chalk = require('chalk');
const fs = require('fs');
const path = require('path');
// Parse command line arguments
const args = process.argv.slice(2);
const runInBackground = args.includes('--background') || args.includes('-b');
const skipChecks = args.includes('--skip-checks') || args.includes('-s');
const buildOnly = args.includes('--build') || args.includes('-B');
const showHelp = args.includes('--help') || args.includes('-h');
// Default ports
const BACKEND_PORT = process.env.BACKEND_PORT || 23816;
const SERVER_PORT = process.env.SERVER_PORT || 3000;
// Display help message
if (showHelp) {
console.log(chalk.cyan(`
╔════════════════════════════════════════════════════════════════╗
║ Pointer IDE - Development Start Script ║
╚════════════════════════════════════════════════════════════════╝
Usage: node start-pointer.js [options]
Options:
-b, --background Run in background mode
-s, --skip-checks Skip connection checks
-B, --build Build only (don't start)
-h, --help Show this help message
Environment Variables:
BACKEND_PORT Backend port (default: 23816)
SERVER_PORT Dev server port (default: 3000)
SKIP_CONNECTION_CHECKS Skip connection validation
Examples:
node start-pointer.js # Normal mode with checks
node start-pointer.js --background # Run in background
node start-pointer.js --build # Build only
BACKEND_PORT=8000 node start-pointer.js # Custom backend port
`));
process.exit(0);
}
// Function to check if a port is in use
async function isPortInUse(port) {
try {
const isInUse = await tcpPortUsed.check(port, '127.0.0.1');
return isInUse;
} catch (error) {
console.error(`Error checking if port ${port} is in use:`, error);
return false;
}
}
// Function to verify project structure and dependencies
async function verifyProjectSetup(strict = false) {
console.log(chalk.blue('\n📋 Verifying project setup...'));
// Check if we're in the root directory with App subfolder
const appDir = path.join(process.cwd(), 'App');
const isSplitStructure = fs.existsSync(appDir);
const checkDir = isSplitStructure ? appDir : process.cwd();
// Essential directories - at least src should exist
const essentialDirs = [
'src'
];
// Optional directories
const optionalDirs = [
'public',
'backend',
'electron',
'server'
];
const requiredFiles = [
'package.json',
'vite.config.ts',
'tsconfig.json'
];
// Check essential directories
for (const dir of essentialDirs) {
if (!fs.existsSync(path.join(checkDir, dir))) {
throw new Error(`❌ Required directory missing: ${dir}`);
}
}
// Check files (only if strict mode)
if (strict) {
for (const file of requiredFiles) {
if (!fs.existsSync(path.join(checkDir, file))) {
throw new Error(`❌ Required file missing: ${file}`);
}
}
}
if (isSplitStructure) {
console.log(chalk.cyan(' 📁 Split structure detected (App/ subdirectory)'));
}
console.log(chalk.green('✅ Project structure verified'));
}
// Function to check if node_modules exists and install if needed
function checkNodeModules(location = '.') {
const modulePath = path.join(location, 'node_modules');
if (!fs.existsSync(modulePath)) {
console.log(chalk.yellow('\n⚠️ node_modules not found. Installing dependencies...'));
const { execSync } = require('child_process');
try {
const cwd = location;
console.log(chalk.blue(` 📦 Installing in: ${cwd}`));
execSync('npm install', { stdio: 'inherit', cwd });
console.log(chalk.green('✅ Dependencies installed'));
} catch (error) {
throw new Error(`Failed to install dependencies in ${location}`);
}
}
}
// Function to ensure script dependencies are available
function ensureScriptDependencies() {
const requiredModules = ['tcp-port-used', 'chalk'];
let missingModules = [];
for (const module of requiredModules) {
try {
require.resolve(module);
} catch (error) {
missingModules.push(module);
}
}
if (missingModules.length > 0) {
console.log(chalk.yellow(`⚠️ Missing script dependencies: ${missingModules.join(', ')}`));
console.log(chalk.yellow(' Installing...\n'));
const { execSync } = require('child_process');
try {
execSync(`npm install ${missingModules.join(' ')}`, { stdio: 'inherit' });
console.log(chalk.green('✅ Script dependencies installed\n'));
} catch (error) {
throw new Error('Failed to install script dependencies');
}
}
}
// Function to build the project
async function buildProject() {
return new Promise((resolve, reject) => {
console.log(chalk.blue('\n🔨 Building project...'));
// Determine build directory
const appDir = path.join(process.cwd(), 'App');
const buildDir = fs.existsSync(appDir) ? appDir : process.cwd();
console.log(chalk.cyan(` 📁 Building in: ${buildDir}`));
// Use npm instead of yarn.cmd (more reliable)
const buildProcess = spawn('npm', ['run', 'build'], {
stdio: 'inherit',
shell: true,
cwd: buildDir
});
buildProcess.on('close', (code) => {
if (code === 0) {
console.log(chalk.green('✅ Build completed'));
resolve();
} else {
reject(new Error(`Build failed with exit code ${code}`));
}
});
buildProcess.on('error', (err) => {
reject(err);
});
});
}
// Function to test backend connection
async function testBackendHealth(port, maxRetries = 5) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const { exec } = require('child_process');
await execAsync(`curl -s http://localhost:${port}/health`, {
timeout: 5000
});
return true;
} catch (error) {
if (attempt < maxRetries) {
console.log(chalk.yellow(` ⏳ Backend health check attempt ${attempt}/${maxRetries}...`));
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
return false;
}
// Function to test server connection
async function testServerHealth(port, maxRetries = 5) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const inUse = await isPortInUse(port);
if (inUse) {
return true;
}
if (attempt < maxRetries) {
console.log(chalk.yellow(` ⏳ Server health check attempt ${attempt}/${maxRetries}...`));
await new Promise(resolve => setTimeout(resolve, 1000));
}
} catch (error) {
if (attempt < maxRetries) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
return false;
}
// Function to find an available port starting from the given base port
async function findAvailablePort(basePort, maxAttempts = 10) {
let port = basePort;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const inUse = await isPortInUse(port);
if (!inUse) {
return port;
}
port++;
}
throw new Error(`Failed to find an available port after ${maxAttempts} attempts starting from ${basePort}`);
}
// Function to start a process with logging
function startProcess(command, args, name, color, env = {}) {
console.log(chalk[color](`\n▶️ Starting ${name}...`));
const options = {
shell: true,
stdio: 'pipe',
env: { ...process.env, ...env }
};
// If running in background mode and this is not the backend process
if (runInBackground && name !== 'Backend') {
options.detached = true;
}
const childProcess = spawn(command, args, options);
childProcess.stdout.on('data', (data) => {
const output = data.toString().trim();
if (output) {
console.log(chalk[color](`[${name}] ${output}`));
}
});
childProcess.stderr.on('data', (data) => {
const output = data.toString().trim();
if (output) {
console.error(chalk.red(`[${name} ERROR] ${output}`));
}
});
childProcess.on('close', (code) => {
if (code !== 0 && code !== null) {
console.log(chalk.red(`[${name}] ⚠️ Process exited with code ${code}`));
} else {
console.log(chalk.green(`[${name}] ✅ Process exited cleanly`));
}
});
childProcess.on('error', (err) => {
console.error(chalk.red(`[${name}] Failed to start: ${err.message}`));
});
return childProcess;
}
// Main function
async function main() {
try {
// Ensure script dependencies first
ensureScriptDependencies();
console.log(chalk.cyan(`
╔════════════════════════════════════════════════════════════════╗
║ Pointer IDE Starter ║
╚════════════════════════════════════════════════════════════════╝
`));
console.log(chalk.blue(`Mode: ${runInBackground ? '🔄 Background' : '⚡ Interactive'}`));
if (skipChecks) {
console.log(chalk.yellow('⚠️ Skip checks mode enabled'));
}
// Verify project setup (non-strict for build mode)
await verifyProjectSetup(false);
// Check root node_modules
checkNodeModules('.');
// Check App node_modules
if (fs.existsSync(path.join(process.cwd(), 'App'))) {
checkNodeModules(path.join(process.cwd(), 'App'));
}
// Build only mode
if (buildOnly) {
await buildProject();
console.log(chalk.green('\n✅ Build completed successfully'));
process.exit(0);
}
// Check if backend is already running
console.log(chalk.blue(`\n🔍 Checking if backend is running on port ${BACKEND_PORT}...`));
let backendRunning = skipChecks ? true : await isPortInUse(BACKEND_PORT);
let backendProcess = null;
if (!backendRunning) {
console.log(chalk.yellow(`Backend not detected on port ${BACKEND_PORT}`));
backendProcess = startProcess('py', ['backend/run.py'], 'Backend', 'yellow');
// Wait for backend to start
console.log(chalk.yellow('⏳ Waiting for backend to initialize...'));
try {
await tcpPortUsed.waitUntilUsed(BACKEND_PORT, 500, 30000);
// Additional health check
if (!skipChecks) {
const isHealthy = await testBackendHealth(BACKEND_PORT);
if (isHealthy) {
console.log(chalk.green('✅ Backend is healthy'));
} else {
console.log(chalk.yellow('⚠️ Backend is running but health check failed'));
}
}
console.log(chalk.green('✅ Backend started successfully!'));
} catch (error) {
console.error(chalk.red('❌ Backend failed to start within timeout period.'));
console.log(chalk.red(' Try: python backend/run.py'));
if (backendProcess) backendProcess.kill();
process.exit(1);
}
} else {
console.log(chalk.green('✅ Backend already running'));
}
// Find available port for server
console.log(chalk.blue(`\n🔍 Finding available port for dev server (starting from ${SERVER_PORT})...`));
const serverPort = await findAvailablePort(SERVER_PORT);
console.log(chalk.blue(`📍 Using port ${serverPort} for dev server`));
// Start server with custom port
const serverProcess = startProcess('npm', ['run', 'dev:server'], 'Server', 'blue', { VITE_PORT: serverPort.toString() });
// Wait for server to start if not skipping checks
if (!skipChecks) {
console.log(chalk.blue(`⏳ Waiting for dev server to initialize on port ${serverPort}...`));
try {
const isHealthy = await testServerHealth(serverPort);
if (isHealthy) {
console.log(chalk.green('✅ Dev server started successfully!'));
} else {
throw new Error('Server health check failed');
}
} catch (error) {
console.error(chalk.red('❌ Dev server failed to start within timeout period.'));
console.log(chalk.red(' Try: yarn dev:server'));
if (backendProcess) backendProcess.kill();
serverProcess.kill();
process.exit(1);
}
} else {
console.log(chalk.yellow('⏭️ Skipping server startup verification'));
}
// Start electron with custom server port
const electronProcess = startProcess('npm', ['run', 'dev:electron'], 'Electron', 'magenta', {
VITE_DEV_SERVER_PORT: serverPort.toString(),
SKIP_CONNECTION_CHECKS: skipChecks ? 'true' : 'false'
});
// If running in background mode, unref the child processes to allow the parent to exit
if (runInBackground) {
console.log(chalk.cyan(`
╔════════════════════════════════════════════════════════════════╗
║ ✅ Pointer running in background ║
║ All services are now active ║
║ Close this terminal to detach ║
╚════════════════════════════════════════════════════════════════╝
`));
if (backendProcess) backendProcess.unref();
serverProcess.unref();
electronProcess.unref();
// Give some time for processes to stabilize before detaching
setTimeout(() => {
console.log(chalk.green('\n🎉 Background mode activated - Terminal can be closed'));
process.exit(0);
}, 5000);
} else {
// Handle graceful shutdown for interactive mode
const cleanup = () => {
console.log(chalk.red('\n\n🛑 Shutting down Pointer...'));
if (backendProcess) {
console.log(chalk.yellow(' • Stopping backend...'));
backendProcess.kill();
}
console.log(chalk.yellow(' • Stopping dev server...'));
serverProcess.kill();
console.log(chalk.yellow(' • Stopping electron...'));
electronProcess.kill();
setTimeout(() => {
console.log(chalk.green('✅ All services stopped'));
process.exit(0);
}, 1000);
};
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
console.log(chalk.cyan(`
╔════════════════════════════════════════════════════════════════╗
║ ✅ Pointer is running ║
║ Backend: http://localhost:${BACKEND_PORT}
║ Server: http://localhost:${serverPort}
║ Press Ctrl+C to stop all services ║
╚════════════════════════════════════════════════════════════════╝
`));
}
} catch (error) {
console.error(chalk.red('\n❌ Error starting Pointer:'));
console.error(chalk.red(` ${error.message}`));
console.log(chalk.yellow('\n💡 Tips:'));
console.log(chalk.yellow(' • Check backend/run.py is valid'));
console.log(chalk.yellow(' • Ensure ports 23816 and 3000 are available'));
console.log(chalk.yellow(' • Run: yarn install first'));
console.log(chalk.yellow(' • View help: node start-pointer.js --help\n'));
process.exit(1);
}
}
// Run the script
main().catch(error => {
console.error('Error running Pointer:', error);
process.exit(1);
});