projax 0.1.29

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 (38) hide show
  1. package/LINKING.md +86 -0
  2. package/README.md +141 -0
  3. package/dist/core/database.d.ts +66 -0
  4. package/dist/core/database.js +312 -0
  5. package/dist/core/detector.d.ts +9 -0
  6. package/dist/core/detector.js +149 -0
  7. package/dist/core/index.d.ts +11 -0
  8. package/dist/core/index.js +43 -0
  9. package/dist/core/scanner.d.ts +8 -0
  10. package/dist/core/scanner.js +114 -0
  11. package/dist/electron/main.d.ts +1 -0
  12. package/dist/electron/main.js +310 -0
  13. package/dist/electron/port-extractor.d.ts +9 -0
  14. package/dist/electron/port-extractor.js +351 -0
  15. package/dist/electron/port-scanner.d.ts +13 -0
  16. package/dist/electron/port-scanner.js +93 -0
  17. package/dist/electron/port-utils.d.ts +21 -0
  18. package/dist/electron/port-utils.js +200 -0
  19. package/dist/electron/preload.d.ts +49 -0
  20. package/dist/electron/preload.js +21 -0
  21. package/dist/electron/renderer/assets/index-BZ6USRnW.js +42 -0
  22. package/dist/electron/renderer/assets/index-DNtxfrZe.js +42 -0
  23. package/dist/electron/renderer/assets/index-khk3K-qG.css +1 -0
  24. package/dist/electron/renderer/index.html +15 -0
  25. package/dist/electron/script-runner.d.ts +40 -0
  26. package/dist/electron/script-runner.js +651 -0
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +971 -0
  29. package/dist/port-extractor.d.ts +9 -0
  30. package/dist/port-extractor.js +351 -0
  31. package/dist/port-scanner.d.ts +13 -0
  32. package/dist/port-scanner.js +93 -0
  33. package/dist/port-utils.d.ts +21 -0
  34. package/dist/port-utils.js +200 -0
  35. package/dist/script-runner.d.ts +40 -0
  36. package/dist/script-runner.js +651 -0
  37. package/package.json +40 -0
  38. package/rebuild-sqlite.js +82 -0
@@ -0,0 +1,651 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.detectProjectType = detectProjectType;
37
+ exports.getProjectScripts = getProjectScripts;
38
+ exports.runScript = runScript;
39
+ exports.getProjectProcesses = getProjectProcesses;
40
+ exports.runScriptInBackground = runScriptInBackground;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const os = __importStar(require("os"));
44
+ const child_process_1 = require("child_process");
45
+ const core_1 = require("./core");
46
+ const port_utils_1 = require("./port-utils");
47
+ /**
48
+ * Detect the project type based on files in the directory
49
+ */
50
+ function detectProjectType(projectPath) {
51
+ if (fs.existsSync(path.join(projectPath, 'package.json'))) {
52
+ return 'node';
53
+ }
54
+ if (fs.existsSync(path.join(projectPath, 'pyproject.toml'))) {
55
+ return 'python';
56
+ }
57
+ if (fs.existsSync(path.join(projectPath, 'Cargo.toml'))) {
58
+ return 'rust';
59
+ }
60
+ if (fs.existsSync(path.join(projectPath, 'go.mod'))) {
61
+ return 'go';
62
+ }
63
+ if (fs.existsSync(path.join(projectPath, 'Makefile')) || fs.existsSync(path.join(projectPath, 'makefile'))) {
64
+ return 'makefile';
65
+ }
66
+ return 'unknown';
67
+ }
68
+ /**
69
+ * Parse scripts from package.json (Node.js)
70
+ */
71
+ function parseNodeScripts(projectPath) {
72
+ const packageJsonPath = path.join(projectPath, 'package.json');
73
+ if (!fs.existsSync(packageJsonPath)) {
74
+ return new Map();
75
+ }
76
+ try {
77
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
78
+ const scripts = packageJson.scripts || {};
79
+ const scriptMap = new Map();
80
+ for (const [name, command] of Object.entries(scripts)) {
81
+ if (typeof command === 'string') {
82
+ scriptMap.set(name, {
83
+ name,
84
+ command: command,
85
+ runner: 'npm',
86
+ projectType: 'node',
87
+ });
88
+ }
89
+ }
90
+ return scriptMap;
91
+ }
92
+ catch (error) {
93
+ return new Map();
94
+ }
95
+ }
96
+ /**
97
+ * Parse scripts from pyproject.toml (Python)
98
+ */
99
+ function parsePythonScripts(projectPath) {
100
+ const pyprojectPath = path.join(projectPath, 'pyproject.toml');
101
+ if (!fs.existsSync(pyprojectPath)) {
102
+ return new Map();
103
+ }
104
+ try {
105
+ const content = fs.readFileSync(pyprojectPath, 'utf-8');
106
+ const scriptMap = new Map();
107
+ // Simple TOML parsing for [project.scripts] section
108
+ // This is a basic implementation - for production, consider using a TOML parser
109
+ const scriptsMatch = content.match(/\[project\.scripts\]\s*\n((?:[^\[]+\n?)+)/);
110
+ if (scriptsMatch) {
111
+ const scriptsSection = scriptsMatch[1];
112
+ const scriptLines = scriptsSection.split('\n');
113
+ for (const line of scriptLines) {
114
+ const match = line.match(/^(\w+)\s*=\s*["']([^"']+)["']/);
115
+ if (match) {
116
+ const [, name, command] = match;
117
+ scriptMap.set(name, {
118
+ name,
119
+ command,
120
+ runner: 'python',
121
+ projectType: 'python',
122
+ });
123
+ }
124
+ }
125
+ }
126
+ // Also check for [tool.poetry.scripts]
127
+ const poetryMatch = content.match(/\[tool\.poetry\.scripts\]\s*\n((?:[^\[]+\n?)+)/);
128
+ if (poetryMatch) {
129
+ const scriptsSection = poetryMatch[1];
130
+ const scriptLines = scriptsSection.split('\n');
131
+ for (const line of scriptLines) {
132
+ const match = line.match(/^(\w+)\s*=\s*["']([^"']+)["']/);
133
+ if (match) {
134
+ const [, name, command] = match;
135
+ scriptMap.set(name, {
136
+ name,
137
+ command,
138
+ runner: 'poetry',
139
+ projectType: 'python',
140
+ });
141
+ }
142
+ }
143
+ }
144
+ return scriptMap;
145
+ }
146
+ catch (error) {
147
+ return new Map();
148
+ }
149
+ }
150
+ /**
151
+ * Parse scripts from Cargo.toml (Rust)
152
+ * Cargo doesn't have a scripts section, but we can detect common cargo commands
153
+ */
154
+ function parseRustScripts(projectPath) {
155
+ const scriptMap = new Map();
156
+ // Common cargo commands that can be run
157
+ const commonCommands = [
158
+ { name: 'build', command: 'cargo build', runner: 'cargo' },
159
+ { name: 'run', command: 'cargo run', runner: 'cargo' },
160
+ { name: 'test', command: 'cargo test', runner: 'cargo' },
161
+ { name: 'check', command: 'cargo check', runner: 'cargo' },
162
+ { name: 'clippy', command: 'cargo clippy', runner: 'cargo' },
163
+ { name: 'fmt', command: 'cargo fmt', runner: 'cargo' },
164
+ ];
165
+ for (const cmd of commonCommands) {
166
+ scriptMap.set(cmd.name, {
167
+ name: cmd.name,
168
+ command: cmd.command,
169
+ runner: cmd.runner,
170
+ projectType: 'rust',
171
+ });
172
+ }
173
+ return scriptMap;
174
+ }
175
+ /**
176
+ * Parse Makefile targets
177
+ */
178
+ function parseMakefileScripts(projectPath) {
179
+ const makefilePath = fs.existsSync(path.join(projectPath, 'Makefile'))
180
+ ? path.join(projectPath, 'Makefile')
181
+ : path.join(projectPath, 'makefile');
182
+ if (!fs.existsSync(makefilePath)) {
183
+ return new Map();
184
+ }
185
+ try {
186
+ const content = fs.readFileSync(makefilePath, 'utf-8');
187
+ const scriptMap = new Map();
188
+ // Parse Makefile targets (basic implementation)
189
+ // Match lines that look like targets: target: dependencies
190
+ const targetRegex = /^([a-zA-Z0-9_-]+)\s*:.*$/gm;
191
+ let match;
192
+ while ((match = targetRegex.exec(content)) !== null) {
193
+ const targetName = match[1];
194
+ // Skip special targets
195
+ if (!targetName.startsWith('.') && targetName !== 'PHONY') {
196
+ scriptMap.set(targetName, {
197
+ name: targetName,
198
+ command: `make ${targetName}`,
199
+ runner: 'make',
200
+ projectType: 'makefile',
201
+ });
202
+ }
203
+ }
204
+ return scriptMap;
205
+ }
206
+ catch (error) {
207
+ return new Map();
208
+ }
209
+ }
210
+ /**
211
+ * Get all available scripts for a project
212
+ */
213
+ function getProjectScripts(projectPath) {
214
+ const type = detectProjectType(projectPath);
215
+ let scripts = new Map();
216
+ switch (type) {
217
+ case 'node':
218
+ scripts = parseNodeScripts(projectPath);
219
+ break;
220
+ case 'python':
221
+ scripts = parsePythonScripts(projectPath);
222
+ break;
223
+ case 'rust':
224
+ scripts = parseRustScripts(projectPath);
225
+ break;
226
+ case 'makefile':
227
+ scripts = parseMakefileScripts(projectPath);
228
+ break;
229
+ case 'go':
230
+ // Go projects typically use Makefile or direct go commands
231
+ // Check for Makefile first, otherwise provide common go commands
232
+ const makefileScripts = parseMakefileScripts(projectPath);
233
+ if (makefileScripts.size > 0) {
234
+ scripts = makefileScripts;
235
+ }
236
+ else {
237
+ // Common go commands
238
+ scripts.set('run', {
239
+ name: 'run',
240
+ command: 'go run .',
241
+ runner: 'go',
242
+ projectType: 'go',
243
+ });
244
+ scripts.set('build', {
245
+ name: 'build',
246
+ command: 'go build',
247
+ runner: 'go',
248
+ projectType: 'go',
249
+ });
250
+ scripts.set('test', {
251
+ name: 'test',
252
+ command: 'go test ./...',
253
+ runner: 'go',
254
+ projectType: 'go',
255
+ });
256
+ }
257
+ break;
258
+ default:
259
+ // For unknown projects, check for Makefile as fallback
260
+ scripts = parseMakefileScripts(projectPath);
261
+ break;
262
+ }
263
+ return {
264
+ type,
265
+ scripts,
266
+ };
267
+ }
268
+ /**
269
+ * Handle port conflict - prompt user or auto-kill based on force flag
270
+ */
271
+ async function handlePortConflict(port, projectName, force) {
272
+ const processInfo = await (0, port_utils_1.getProcessOnPort)(port);
273
+ if (!processInfo) {
274
+ console.error(`Port ${port} appears to be in use, but couldn't identify the process.`);
275
+ return false;
276
+ }
277
+ console.error(`\n⚠️ Port ${port} is already in use by process ${processInfo.pid} (${processInfo.command})`);
278
+ if (force) {
279
+ console.log(`Killing process ${processInfo.pid} on port ${port}...`);
280
+ const killed = await (0, port_utils_1.killProcessOnPort)(port);
281
+ if (killed) {
282
+ console.log(`✓ Process killed. Retrying...\n`);
283
+ return true;
284
+ }
285
+ else {
286
+ console.error(`Failed to kill process on port ${port}`);
287
+ return false;
288
+ }
289
+ }
290
+ else {
291
+ const inquirer = (await Promise.resolve().then(() => __importStar(require('inquirer')))).default;
292
+ const answer = await inquirer.prompt([
293
+ {
294
+ type: 'confirm',
295
+ name: 'kill',
296
+ message: `Kill process ${processInfo.pid} (${processInfo.command}) and continue?`,
297
+ default: false,
298
+ },
299
+ ]);
300
+ if (answer.kill) {
301
+ const killed = await (0, port_utils_1.killProcessOnPort)(port);
302
+ if (killed) {
303
+ console.log(`✓ Process killed. Retrying...\n`);
304
+ return true;
305
+ }
306
+ else {
307
+ console.error(`Failed to kill process on port ${port}`);
308
+ return false;
309
+ }
310
+ }
311
+ else {
312
+ console.log('Cancelled.');
313
+ return false;
314
+ }
315
+ }
316
+ }
317
+ /**
318
+ * Check ports proactively before script execution
319
+ */
320
+ async function checkPortsBeforeExecution(projectPath, scriptName, force) {
321
+ const db = (0, core_1.getDatabaseManager)();
322
+ const project = db.getProjectByPath(projectPath);
323
+ if (!project)
324
+ return true; // Can't check if project not in DB
325
+ const ports = db.getProjectPortsByScript(project.id, scriptName);
326
+ if (ports.length === 0) {
327
+ // Also check ports without script name (general project ports)
328
+ const allPorts = db.getProjectPorts(project.id);
329
+ for (const portInfo of allPorts) {
330
+ const inUse = await (0, port_utils_1.detectPortInUse)(portInfo.port);
331
+ if (inUse) {
332
+ return await handlePortConflict(portInfo.port, project.name, force);
333
+ }
334
+ }
335
+ return true;
336
+ }
337
+ for (const portInfo of ports) {
338
+ const inUse = await (0, port_utils_1.detectPortInUse)(portInfo.port);
339
+ if (inUse) {
340
+ return await handlePortConflict(portInfo.port, project.name, force);
341
+ }
342
+ }
343
+ return true;
344
+ }
345
+ /**
346
+ * Execute a script in a project directory
347
+ */
348
+ function runScript(projectPath, scriptName, args = [], force = false) {
349
+ return new Promise(async (resolve, reject) => {
350
+ const projectScripts = getProjectScripts(projectPath);
351
+ const script = projectScripts.scripts.get(scriptName);
352
+ if (!script) {
353
+ reject(new Error(`Script "${scriptName}" not found in project`));
354
+ return;
355
+ }
356
+ // Proactive port checking
357
+ const canProceed = await checkPortsBeforeExecution(projectPath, scriptName, force);
358
+ if (!canProceed) {
359
+ reject(new Error('Port conflict not resolved'));
360
+ return;
361
+ }
362
+ let command;
363
+ let commandArgs;
364
+ switch (script.runner) {
365
+ case 'npm':
366
+ command = 'npm';
367
+ commandArgs = ['run', scriptName, ...args];
368
+ break;
369
+ case 'yarn':
370
+ command = 'yarn';
371
+ commandArgs = [scriptName, ...args];
372
+ break;
373
+ case 'pnpm':
374
+ command = 'pnpm';
375
+ commandArgs = ['run', scriptName, ...args];
376
+ break;
377
+ case 'python':
378
+ command = 'python';
379
+ commandArgs = ['-m', ...script.command.split(' ').slice(1), ...args];
380
+ break;
381
+ case 'poetry':
382
+ command = 'poetry';
383
+ // For poetry, the command is already the module path, use 'run' to execute it
384
+ const modulePath = script.command.split(' ').slice(1).join(' ');
385
+ commandArgs = ['run', 'python', '-m', modulePath, ...args];
386
+ break;
387
+ case 'cargo':
388
+ command = 'cargo';
389
+ commandArgs = scriptName === 'run' ? ['run', ...args] : [scriptName, ...args];
390
+ break;
391
+ case 'go':
392
+ command = 'go';
393
+ commandArgs = script.command.split(' ').slice(1);
394
+ if (scriptName === 'run' && args.length > 0) {
395
+ commandArgs = ['run', ...args];
396
+ }
397
+ else {
398
+ commandArgs = [...commandArgs, ...args];
399
+ }
400
+ break;
401
+ case 'make':
402
+ command = 'make';
403
+ commandArgs = [scriptName, ...args];
404
+ break;
405
+ default:
406
+ // Fallback: try to run the command directly
407
+ const parts = script.command.split(' ');
408
+ command = parts[0];
409
+ commandArgs = [...parts.slice(1), ...args];
410
+ break;
411
+ }
412
+ console.log(`Running: ${command} ${commandArgs.join(' ')}`);
413
+ console.log(`In directory: ${projectPath}\n`);
414
+ // Capture stderr for reactive port conflict detection
415
+ let stderrOutput = '';
416
+ let stdoutOutput = '';
417
+ const child = (0, child_process_1.spawn)(command, commandArgs, {
418
+ cwd: projectPath,
419
+ stdio: ['inherit', 'pipe', 'pipe'],
420
+ shell: process.platform === 'win32',
421
+ });
422
+ // Capture output for error analysis
423
+ if (child.stdout) {
424
+ child.stdout.on('data', (data) => {
425
+ stdoutOutput += data.toString();
426
+ process.stdout.write(data);
427
+ });
428
+ }
429
+ if (child.stderr) {
430
+ child.stderr.on('data', (data) => {
431
+ stderrOutput += data.toString();
432
+ process.stderr.write(data);
433
+ });
434
+ }
435
+ child.on('close', async (code) => {
436
+ if (code === 0) {
437
+ resolve(0);
438
+ }
439
+ else {
440
+ // Reactive port conflict detection
441
+ const errorOutput = stderrOutput + stdoutOutput;
442
+ const port = (0, port_utils_1.extractPortFromError)(errorOutput);
443
+ if (port) {
444
+ const db = (0, core_1.getDatabaseManager)();
445
+ const project = db.getProjectByPath(projectPath);
446
+ const projectName = project?.name || 'project';
447
+ const resolved = await handlePortConflict(port, projectName, force);
448
+ if (resolved) {
449
+ // Retry script execution
450
+ try {
451
+ const retryResult = await runScript(projectPath, scriptName, args, force);
452
+ resolve(retryResult);
453
+ }
454
+ catch (retryError) {
455
+ reject(retryError);
456
+ }
457
+ }
458
+ else {
459
+ reject(new Error(`Script exited with code ${code} (port ${port} conflict)`));
460
+ }
461
+ }
462
+ else {
463
+ reject(new Error(`Script exited with code ${code}`));
464
+ }
465
+ }
466
+ });
467
+ child.on('error', (error) => {
468
+ reject(new Error(`Failed to execute script: ${error.message}`));
469
+ });
470
+ });
471
+ }
472
+ /**
473
+ * Get the path to the processes file
474
+ */
475
+ function getProcessesFilePath() {
476
+ const dataDir = path.join(os.homedir(), '.projax');
477
+ if (!fs.existsSync(dataDir)) {
478
+ fs.mkdirSync(dataDir, { recursive: true });
479
+ }
480
+ return path.join(dataDir, 'processes.json');
481
+ }
482
+ /**
483
+ * Load running processes from disk
484
+ */
485
+ function loadProcesses() {
486
+ const filePath = getProcessesFilePath();
487
+ if (!fs.existsSync(filePath)) {
488
+ return [];
489
+ }
490
+ try {
491
+ const content = fs.readFileSync(filePath, 'utf-8');
492
+ return JSON.parse(content);
493
+ }
494
+ catch (error) {
495
+ return [];
496
+ }
497
+ }
498
+ /**
499
+ * Save running processes to disk
500
+ */
501
+ function saveProcesses(processes) {
502
+ const filePath = getProcessesFilePath();
503
+ fs.writeFileSync(filePath, JSON.stringify(processes, null, 2));
504
+ }
505
+ /**
506
+ * Add a process to the tracking file
507
+ */
508
+ function addProcess(process) {
509
+ const processes = loadProcesses();
510
+ processes.push(process);
511
+ saveProcesses(processes);
512
+ }
513
+ /**
514
+ * Remove a process from tracking by PID
515
+ */
516
+ function removeProcess(pid) {
517
+ const processes = loadProcesses();
518
+ const filtered = processes.filter(p => p.pid !== pid);
519
+ saveProcesses(filtered);
520
+ }
521
+ /**
522
+ * Get all running processes for a project
523
+ */
524
+ function getProjectProcesses(projectPath) {
525
+ const processes = loadProcesses();
526
+ return processes.filter(p => p.projectPath === projectPath);
527
+ }
528
+ /**
529
+ * Execute a script in the background (minimal logging)
530
+ */
531
+ function runScriptInBackground(projectPath, projectName, scriptName, args = [], force = false) {
532
+ return new Promise(async (resolve, reject) => {
533
+ const projectScripts = getProjectScripts(projectPath);
534
+ const script = projectScripts.scripts.get(scriptName);
535
+ if (!script) {
536
+ reject(new Error(`Script "${scriptName}" not found in project`));
537
+ return;
538
+ }
539
+ // Proactive port checking
540
+ const canProceed = await checkPortsBeforeExecution(projectPath, scriptName, force);
541
+ if (!canProceed) {
542
+ reject(new Error('Port conflict not resolved'));
543
+ return;
544
+ }
545
+ let command;
546
+ let commandArgs;
547
+ switch (script.runner) {
548
+ case 'npm':
549
+ command = 'npm';
550
+ commandArgs = ['run', scriptName, ...args];
551
+ break;
552
+ case 'yarn':
553
+ command = 'yarn';
554
+ commandArgs = [scriptName, ...args];
555
+ break;
556
+ case 'pnpm':
557
+ command = 'pnpm';
558
+ commandArgs = ['run', scriptName, ...args];
559
+ break;
560
+ case 'python':
561
+ command = 'python';
562
+ commandArgs = ['-m', ...script.command.split(' ').slice(1), ...args];
563
+ break;
564
+ case 'poetry':
565
+ command = 'poetry';
566
+ const modulePath = script.command.split(' ').slice(1).join(' ');
567
+ commandArgs = ['run', 'python', '-m', modulePath, ...args];
568
+ break;
569
+ case 'cargo':
570
+ command = 'cargo';
571
+ commandArgs = scriptName === 'run' ? ['run', ...args] : [scriptName, ...args];
572
+ break;
573
+ case 'go':
574
+ command = 'go';
575
+ commandArgs = script.command.split(' ').slice(1);
576
+ if (scriptName === 'run' && args.length > 0) {
577
+ commandArgs = ['run', ...args];
578
+ }
579
+ else {
580
+ commandArgs = [...commandArgs, ...args];
581
+ }
582
+ break;
583
+ case 'make':
584
+ command = 'make';
585
+ commandArgs = [scriptName, ...args];
586
+ break;
587
+ default:
588
+ const parts = script.command.split(' ');
589
+ command = parts[0];
590
+ commandArgs = [...parts.slice(1), ...args];
591
+ break;
592
+ }
593
+ // Create log file for the process
594
+ const dataDir = path.join(os.homedir(), '.projax');
595
+ const logsDir = path.join(dataDir, 'logs');
596
+ if (!fs.existsSync(logsDir)) {
597
+ fs.mkdirSync(logsDir, { recursive: true });
598
+ }
599
+ const logFile = path.join(logsDir, `process-${Date.now()}-${scriptName}.log`);
600
+ // Create write stream for log file (keep reference to prevent GC)
601
+ const logStream = fs.createWriteStream(logFile, { flags: 'a' });
602
+ // Spawn process in detached mode with output redirected to log file
603
+ const child = (0, child_process_1.spawn)(command, commandArgs, {
604
+ cwd: projectPath,
605
+ stdio: ['ignore', logStream, logStream], // Redirect stdout and stderr to log file
606
+ detached: true,
607
+ shell: process.platform === 'win32',
608
+ });
609
+ // Don't close the stream - let it stay open for the child process
610
+ // The stream will be closed when the child process exits
611
+ // Store process info
612
+ const processInfo = {
613
+ pid: child.pid,
614
+ projectPath,
615
+ projectName,
616
+ scriptName,
617
+ command: `${command} ${commandArgs.join(' ')}`,
618
+ startedAt: Date.now(),
619
+ logFile,
620
+ };
621
+ addProcess(processInfo);
622
+ // Unref so parent can exit and process runs independently
623
+ child.unref();
624
+ // Show minimal output
625
+ console.log(`✓ Started "${projectName}" (${scriptName}) in background [PID: ${child.pid}]`);
626
+ console.log(` Logs: ${logFile}`);
627
+ console.log(` Command: ${command} ${commandArgs.join(' ')}\n`);
628
+ // For background processes, we can't easily do reactive detection
629
+ // But we can check the log file after a short delay for port conflicts
630
+ setTimeout(async () => {
631
+ try {
632
+ // Wait a bit for process to start and potentially fail
633
+ await new Promise(resolve => setTimeout(resolve, 2000));
634
+ if (fs.existsSync(logFile)) {
635
+ const logContent = fs.readFileSync(logFile, 'utf-8');
636
+ const port = (0, port_utils_1.extractPortFromError)(logContent);
637
+ if (port) {
638
+ console.error(`\n⚠️ Port conflict detected in background process: port ${port} is in use`);
639
+ console.error(` Check log file: ${logFile}`);
640
+ console.error(` Use: prx <project> <script> --force to auto-resolve port conflicts\n`);
641
+ }
642
+ }
643
+ }
644
+ catch {
645
+ // Ignore errors checking log file
646
+ }
647
+ }, 3000);
648
+ // Resolve immediately since process is running in background
649
+ resolve(0);
650
+ });
651
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};