projax 1.3.8 → 1.3.10
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/README.md +72 -5
- package/dist/electron/main.js +35 -5
- package/dist/electron/preload.d.ts +6 -0
- package/dist/electron/preload.js +1 -0
- package/dist/electron/renderer/assets/index-BTFVUZ9M.js +46 -0
- package/dist/electron/renderer/assets/index-D5r8Ki4V.js +46 -0
- package/dist/electron/renderer/assets/index-DtjxFwRT.css +1 -0
- package/dist/electron/renderer/index.html +2 -2
- package/dist/electron/script-runner.d.ts +5 -1
- package/dist/electron/script-runner.js +80 -9
- package/dist/index.js +126 -6
- package/dist/script-runner.d.ts +5 -1
- package/dist/script-runner.js +80 -9
- package/package.json +1 -1
|
@@ -40,6 +40,7 @@ exports.loadProcesses = loadProcesses;
|
|
|
40
40
|
exports.removeProcess = removeProcess;
|
|
41
41
|
exports.getProjectProcesses = getProjectProcesses;
|
|
42
42
|
exports.getRunningProcesses = getRunningProcesses;
|
|
43
|
+
exports.getRunningProcessesClean = getRunningProcessesClean;
|
|
43
44
|
exports.stopScript = stopScript;
|
|
44
45
|
exports.stopScriptByPort = stopScriptByPort;
|
|
45
46
|
exports.stopProjectProcesses = stopProjectProcesses;
|
|
@@ -532,11 +533,61 @@ function getProjectProcesses(projectPath) {
|
|
|
532
533
|
return processes.filter(p => p.projectPath === projectPath);
|
|
533
534
|
}
|
|
534
535
|
/**
|
|
535
|
-
*
|
|
536
|
+
* Check if a process is still running
|
|
537
|
+
*/
|
|
538
|
+
async function isProcessRunning(pid) {
|
|
539
|
+
try {
|
|
540
|
+
if (os.platform() === 'win32') {
|
|
541
|
+
const { exec } = require('child_process');
|
|
542
|
+
const { promisify } = require('util');
|
|
543
|
+
const execAsync = promisify(exec);
|
|
544
|
+
const { stdout } = await execAsync(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`);
|
|
545
|
+
return stdout.includes(`"${pid}"`);
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
// On Unix-like systems, kill with signal 0 checks if process exists
|
|
549
|
+
try {
|
|
550
|
+
process.kill(pid, 0);
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
catch {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Clean up dead processes from tracking
|
|
564
|
+
*/
|
|
565
|
+
async function cleanupDeadProcesses() {
|
|
566
|
+
const processes = loadProcesses();
|
|
567
|
+
const aliveProcesses = [];
|
|
568
|
+
for (const proc of processes) {
|
|
569
|
+
const isAlive = await isProcessRunning(proc.pid);
|
|
570
|
+
if (isAlive) {
|
|
571
|
+
aliveProcesses.push(proc);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (aliveProcesses.length !== processes.length) {
|
|
575
|
+
saveProcesses(aliveProcesses);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Get all running processes (synchronous version that returns potentially stale data)
|
|
536
580
|
*/
|
|
537
581
|
function getRunningProcesses() {
|
|
538
582
|
return loadProcesses();
|
|
539
583
|
}
|
|
584
|
+
/**
|
|
585
|
+
* Get all running processes with cleanup (async version)
|
|
586
|
+
*/
|
|
587
|
+
async function getRunningProcessesClean() {
|
|
588
|
+
await cleanupDeadProcesses();
|
|
589
|
+
return loadProcesses();
|
|
590
|
+
}
|
|
540
591
|
/**
|
|
541
592
|
* Extract URLs from text output
|
|
542
593
|
*/
|
|
@@ -744,35 +795,43 @@ function runScriptInBackground(projectPath, projectName, scriptName, args = [],
|
|
|
744
795
|
fs.mkdirSync(logsDir, { recursive: true });
|
|
745
796
|
}
|
|
746
797
|
const logFile = path.join(logsDir, `process-${Date.now()}-${scriptName}.log`);
|
|
747
|
-
//
|
|
748
|
-
const
|
|
798
|
+
// Open log file with file descriptors that can be inherited by child process
|
|
799
|
+
const logFd = fs.openSync(logFile, 'a');
|
|
749
800
|
// Spawn process in detached mode with output redirected to log file
|
|
750
801
|
let child;
|
|
751
802
|
try {
|
|
752
803
|
child = (0, child_process_1.spawn)(command, commandArgs, {
|
|
753
804
|
cwd: projectPath,
|
|
754
|
-
stdio: ['ignore',
|
|
805
|
+
stdio: ['ignore', logFd, logFd], // Redirect stdout and stderr to log file descriptor
|
|
755
806
|
detached: true,
|
|
756
807
|
shell: process.platform === 'win32',
|
|
757
808
|
});
|
|
758
809
|
}
|
|
759
810
|
catch (spawnError) {
|
|
760
|
-
|
|
811
|
+
fs.closeSync(logFd);
|
|
761
812
|
reject(new Error(`Failed to spawn process: ${spawnError instanceof Error ? spawnError.message : String(spawnError)}`));
|
|
762
813
|
return;
|
|
763
814
|
}
|
|
764
815
|
if (!child.pid) {
|
|
765
|
-
|
|
816
|
+
fs.closeSync(logFd);
|
|
766
817
|
reject(new Error('Failed to start process: no PID assigned'));
|
|
767
818
|
return;
|
|
768
819
|
}
|
|
769
820
|
// Handle spawn errors
|
|
770
821
|
child.on('error', (error) => {
|
|
771
|
-
|
|
822
|
+
fs.closeSync(logFd);
|
|
772
823
|
reject(new Error(`Process spawn error: ${error.message}`));
|
|
773
824
|
});
|
|
774
|
-
//
|
|
775
|
-
// The
|
|
825
|
+
// Close the file descriptor in parent process after a short delay
|
|
826
|
+
// The child process will have its own copy
|
|
827
|
+
setTimeout(() => {
|
|
828
|
+
try {
|
|
829
|
+
fs.closeSync(logFd);
|
|
830
|
+
}
|
|
831
|
+
catch {
|
|
832
|
+
// Already closed or error, ignore
|
|
833
|
+
}
|
|
834
|
+
}, 1000);
|
|
776
835
|
// Store process info
|
|
777
836
|
const processInfo = {
|
|
778
837
|
pid: child.pid,
|
|
@@ -784,6 +843,18 @@ function runScriptInBackground(projectPath, projectName, scriptName, args = [],
|
|
|
784
843
|
logFile,
|
|
785
844
|
};
|
|
786
845
|
addProcess(processInfo);
|
|
846
|
+
// Listen for process exit to clean up tracking
|
|
847
|
+
child.on('exit', (code, signal) => {
|
|
848
|
+
// Remove from tracking when process exits
|
|
849
|
+
removeProcess(child.pid);
|
|
850
|
+
// Log exit information
|
|
851
|
+
if (code !== null) {
|
|
852
|
+
fs.appendFileSync(logFile, `\n\n[Process exited with code ${code}]\n`);
|
|
853
|
+
}
|
|
854
|
+
else if (signal !== null) {
|
|
855
|
+
fs.appendFileSync(logFile, `\n\n[Process killed by signal ${signal}]\n`);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
787
858
|
// Unref so parent can exit and process runs independently
|
|
788
859
|
child.unref();
|
|
789
860
|
// Show minimal output
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
37
37
|
const commander_1 = require("commander");
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
|
+
const os = __importStar(require("os"));
|
|
40
41
|
const http = __importStar(require("http"));
|
|
41
42
|
const core_bridge_1 = require("./core-bridge");
|
|
42
43
|
const script_runner_1 = require("./script-runner");
|
|
@@ -724,7 +725,7 @@ program
|
|
|
724
725
|
// CD command - change to project directory (outputs shell command for eval)
|
|
725
726
|
program
|
|
726
727
|
.command('cd')
|
|
727
|
-
.description('Change to a project directory
|
|
728
|
+
.description('Change to a project directory')
|
|
728
729
|
.argument('[project]', 'Project ID or name (leave empty for interactive selection)')
|
|
729
730
|
.action(async (projectIdentifier) => {
|
|
730
731
|
try {
|
|
@@ -762,17 +763,117 @@ program
|
|
|
762
763
|
console.error('Error: No project selected');
|
|
763
764
|
process.exit(1);
|
|
764
765
|
}
|
|
765
|
-
// Output a shell command that
|
|
766
|
-
//
|
|
767
|
-
// Or create a shell function: prxcd() { eval $(prx cd "$@"); }
|
|
766
|
+
// Output a shell command that changes directory
|
|
767
|
+
// To use: eval "$(prx cd <project>)"
|
|
768
768
|
const escapedPath = project.path.replace(/'/g, "'\\''");
|
|
769
|
-
console.log(`cd '${escapedPath}'`);
|
|
769
|
+
console.log(`cd '${escapedPath}' && echo "Changed to: ${project.name}"`);
|
|
770
770
|
}
|
|
771
771
|
catch (error) {
|
|
772
772
|
console.error('Error changing to project directory:', error instanceof Error ? error.message : error);
|
|
773
773
|
process.exit(1);
|
|
774
774
|
}
|
|
775
775
|
});
|
|
776
|
+
// Run script command
|
|
777
|
+
program
|
|
778
|
+
.command('run <project> <script>')
|
|
779
|
+
.description('Run a script from a project')
|
|
780
|
+
.option('-b, --background', 'Run script in background')
|
|
781
|
+
.option('-f, --force', 'Force run (kill conflicting processes on ports)')
|
|
782
|
+
.action(async (projectIdentifier, scriptName, options) => {
|
|
783
|
+
try {
|
|
784
|
+
const projects = (0, core_bridge_1.getAllProjects)();
|
|
785
|
+
// Find project by ID or name
|
|
786
|
+
let project;
|
|
787
|
+
const numericId = parseInt(projectIdentifier, 10);
|
|
788
|
+
if (!isNaN(numericId)) {
|
|
789
|
+
project = projects.find((p) => p.id === numericId);
|
|
790
|
+
}
|
|
791
|
+
if (!project) {
|
|
792
|
+
project = projects.find((p) => p.name === projectIdentifier);
|
|
793
|
+
}
|
|
794
|
+
if (!project) {
|
|
795
|
+
console.error(`Error: Project not found: ${projectIdentifier}`);
|
|
796
|
+
process.exit(1);
|
|
797
|
+
}
|
|
798
|
+
const projectScripts = (0, script_runner_1.getProjectScripts)(project.path);
|
|
799
|
+
if (!projectScripts.scripts.has(scriptName)) {
|
|
800
|
+
console.error(`Error: Script "${scriptName}" not found in project "${project.name}"`);
|
|
801
|
+
console.error(`\nAvailable scripts:`);
|
|
802
|
+
projectScripts.scripts.forEach((script) => {
|
|
803
|
+
console.error(` ${script.name}`);
|
|
804
|
+
});
|
|
805
|
+
process.exit(1);
|
|
806
|
+
}
|
|
807
|
+
if (options.background) {
|
|
808
|
+
await (0, script_runner_1.runScriptInBackground)(project.path, project.name, scriptName, [], options.force || false);
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
await (0, script_runner_1.runScript)(project.path, scriptName, [], options.force || false);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
catch (error) {
|
|
815
|
+
console.error('Error running script:', error instanceof Error ? error.message : error);
|
|
816
|
+
process.exit(1);
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
// List running processes command
|
|
820
|
+
program
|
|
821
|
+
.command('ps')
|
|
822
|
+
.description('List running background processes')
|
|
823
|
+
.action(async () => {
|
|
824
|
+
try {
|
|
825
|
+
const { getRunningProcessesClean } = await Promise.resolve().then(() => __importStar(require('./script-runner')));
|
|
826
|
+
const processes = await getRunningProcessesClean();
|
|
827
|
+
if (processes.length === 0) {
|
|
828
|
+
console.log('No running background processes.');
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
console.log(`\nRunning processes (${processes.length}):\n`);
|
|
832
|
+
for (const proc of processes) {
|
|
833
|
+
const uptime = Math.floor((Date.now() - proc.startedAt) / 1000);
|
|
834
|
+
const minutes = Math.floor(uptime / 60);
|
|
835
|
+
const seconds = uptime % 60;
|
|
836
|
+
const uptimeStr = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
|
837
|
+
console.log(` PID ${proc.pid}: ${proc.projectName} (${proc.scriptName}) - ${uptimeStr}`);
|
|
838
|
+
console.log(` Command: ${proc.command}`);
|
|
839
|
+
console.log(` Logs: ${proc.logFile}`);
|
|
840
|
+
if (proc.detectedUrls && proc.detectedUrls.length > 0) {
|
|
841
|
+
console.log(` URLs: ${proc.detectedUrls.join(', ')}`);
|
|
842
|
+
}
|
|
843
|
+
console.log('');
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
catch (error) {
|
|
847
|
+
console.error('Error listing processes:', error instanceof Error ? error.message : error);
|
|
848
|
+
process.exit(1);
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
// Stop process command
|
|
852
|
+
program
|
|
853
|
+
.command('stop <pid>')
|
|
854
|
+
.description('Stop a running background process')
|
|
855
|
+
.action(async (pidStr) => {
|
|
856
|
+
try {
|
|
857
|
+
const pid = parseInt(pidStr, 10);
|
|
858
|
+
if (isNaN(pid)) {
|
|
859
|
+
console.error('Error: Invalid PID');
|
|
860
|
+
process.exit(1);
|
|
861
|
+
}
|
|
862
|
+
const { stopScript } = await Promise.resolve().then(() => __importStar(require('./script-runner')));
|
|
863
|
+
const success = await stopScript(pid);
|
|
864
|
+
if (success) {
|
|
865
|
+
console.log(`✓ Stopped process ${pid}`);
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
console.error(`Failed to stop process ${pid}`);
|
|
869
|
+
process.exit(1);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
catch (error) {
|
|
873
|
+
console.error('Error stopping process:', error instanceof Error ? error.message : error);
|
|
874
|
+
process.exit(1);
|
|
875
|
+
}
|
|
876
|
+
});
|
|
776
877
|
// Start Desktop UI command
|
|
777
878
|
program
|
|
778
879
|
.command('web')
|
|
@@ -782,6 +883,25 @@ program
|
|
|
782
883
|
.option('--dev', 'Start in development mode (with hot reload)')
|
|
783
884
|
.action(async (options) => {
|
|
784
885
|
try {
|
|
886
|
+
// Clear Electron cache to prevent stale module issues
|
|
887
|
+
if (os.platform() === 'darwin') {
|
|
888
|
+
const cacheDirs = [
|
|
889
|
+
path.join(os.homedir(), 'Library', 'Application Support', 'Electron', 'Cache'),
|
|
890
|
+
path.join(os.homedir(), 'Library', 'Application Support', 'Electron', 'Code Cache'),
|
|
891
|
+
path.join(os.homedir(), 'Library', 'Application Support', 'Electron', 'GPUCache'),
|
|
892
|
+
path.join(os.homedir(), 'Library', 'Caches', 'Electron'),
|
|
893
|
+
];
|
|
894
|
+
for (const dir of cacheDirs) {
|
|
895
|
+
try {
|
|
896
|
+
if (fs.existsSync(dir)) {
|
|
897
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
catch (error) {
|
|
901
|
+
// Ignore cache clear errors
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
785
905
|
// Ensure API server is running before starting Desktop app
|
|
786
906
|
await ensureAPIServerRunning(false);
|
|
787
907
|
// Check for bundled Desktop app first (in dist/desktop when installed globally)
|
|
@@ -1031,7 +1151,7 @@ program
|
|
|
1031
1151
|
// Check if first argument is not a known command
|
|
1032
1152
|
(async () => {
|
|
1033
1153
|
const args = process.argv.slice(2);
|
|
1034
|
-
const knownCommands = ['prxi', 'i', 'add', 'list', 'scan', 'remove', 'rn', 'rename', 'cd', 'pwd', 'web', 'desktop', 'ui', 'scripts', 'scan-ports', 'api', '--help', '-h', '--version', '-V'];
|
|
1154
|
+
const knownCommands = ['prxi', 'i', 'add', 'list', 'scan', 'remove', 'rn', 'rename', 'cd', 'pwd', 'run', 'ps', 'stop', 'web', 'desktop', 'ui', 'scripts', 'scan-ports', 'api', '--help', '-h', '--version', '-V'];
|
|
1035
1155
|
// If we have at least 1 argument and first is not a known command, treat as project identifier
|
|
1036
1156
|
if (args.length >= 1 && !knownCommands.includes(args[0])) {
|
|
1037
1157
|
const projectIdentifier = args[0];
|
package/dist/script-runner.d.ts
CHANGED
|
@@ -44,9 +44,13 @@ export declare function removeProcess(pid: number): void;
|
|
|
44
44
|
*/
|
|
45
45
|
export declare function getProjectProcesses(projectPath: string): BackgroundProcess[];
|
|
46
46
|
/**
|
|
47
|
-
* Get all running processes
|
|
47
|
+
* Get all running processes (synchronous version that returns potentially stale data)
|
|
48
48
|
*/
|
|
49
49
|
export declare function getRunningProcesses(): BackgroundProcess[];
|
|
50
|
+
/**
|
|
51
|
+
* Get all running processes with cleanup (async version)
|
|
52
|
+
*/
|
|
53
|
+
export declare function getRunningProcessesClean(): Promise<BackgroundProcess[]>;
|
|
50
54
|
/**
|
|
51
55
|
* Stop a script by PID
|
|
52
56
|
*/
|
package/dist/script-runner.js
CHANGED
|
@@ -40,6 +40,7 @@ exports.loadProcesses = loadProcesses;
|
|
|
40
40
|
exports.removeProcess = removeProcess;
|
|
41
41
|
exports.getProjectProcesses = getProjectProcesses;
|
|
42
42
|
exports.getRunningProcesses = getRunningProcesses;
|
|
43
|
+
exports.getRunningProcessesClean = getRunningProcessesClean;
|
|
43
44
|
exports.stopScript = stopScript;
|
|
44
45
|
exports.stopScriptByPort = stopScriptByPort;
|
|
45
46
|
exports.stopProjectProcesses = stopProjectProcesses;
|
|
@@ -532,11 +533,61 @@ function getProjectProcesses(projectPath) {
|
|
|
532
533
|
return processes.filter(p => p.projectPath === projectPath);
|
|
533
534
|
}
|
|
534
535
|
/**
|
|
535
|
-
*
|
|
536
|
+
* Check if a process is still running
|
|
537
|
+
*/
|
|
538
|
+
async function isProcessRunning(pid) {
|
|
539
|
+
try {
|
|
540
|
+
if (os.platform() === 'win32') {
|
|
541
|
+
const { exec } = require('child_process');
|
|
542
|
+
const { promisify } = require('util');
|
|
543
|
+
const execAsync = promisify(exec);
|
|
544
|
+
const { stdout } = await execAsync(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`);
|
|
545
|
+
return stdout.includes(`"${pid}"`);
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
// On Unix-like systems, kill with signal 0 checks if process exists
|
|
549
|
+
try {
|
|
550
|
+
process.kill(pid, 0);
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
catch {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Clean up dead processes from tracking
|
|
564
|
+
*/
|
|
565
|
+
async function cleanupDeadProcesses() {
|
|
566
|
+
const processes = loadProcesses();
|
|
567
|
+
const aliveProcesses = [];
|
|
568
|
+
for (const proc of processes) {
|
|
569
|
+
const isAlive = await isProcessRunning(proc.pid);
|
|
570
|
+
if (isAlive) {
|
|
571
|
+
aliveProcesses.push(proc);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (aliveProcesses.length !== processes.length) {
|
|
575
|
+
saveProcesses(aliveProcesses);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Get all running processes (synchronous version that returns potentially stale data)
|
|
536
580
|
*/
|
|
537
581
|
function getRunningProcesses() {
|
|
538
582
|
return loadProcesses();
|
|
539
583
|
}
|
|
584
|
+
/**
|
|
585
|
+
* Get all running processes with cleanup (async version)
|
|
586
|
+
*/
|
|
587
|
+
async function getRunningProcessesClean() {
|
|
588
|
+
await cleanupDeadProcesses();
|
|
589
|
+
return loadProcesses();
|
|
590
|
+
}
|
|
540
591
|
/**
|
|
541
592
|
* Extract URLs from text output
|
|
542
593
|
*/
|
|
@@ -744,35 +795,43 @@ function runScriptInBackground(projectPath, projectName, scriptName, args = [],
|
|
|
744
795
|
fs.mkdirSync(logsDir, { recursive: true });
|
|
745
796
|
}
|
|
746
797
|
const logFile = path.join(logsDir, `process-${Date.now()}-${scriptName}.log`);
|
|
747
|
-
//
|
|
748
|
-
const
|
|
798
|
+
// Open log file with file descriptors that can be inherited by child process
|
|
799
|
+
const logFd = fs.openSync(logFile, 'a');
|
|
749
800
|
// Spawn process in detached mode with output redirected to log file
|
|
750
801
|
let child;
|
|
751
802
|
try {
|
|
752
803
|
child = (0, child_process_1.spawn)(command, commandArgs, {
|
|
753
804
|
cwd: projectPath,
|
|
754
|
-
stdio: ['ignore',
|
|
805
|
+
stdio: ['ignore', logFd, logFd], // Redirect stdout and stderr to log file descriptor
|
|
755
806
|
detached: true,
|
|
756
807
|
shell: process.platform === 'win32',
|
|
757
808
|
});
|
|
758
809
|
}
|
|
759
810
|
catch (spawnError) {
|
|
760
|
-
|
|
811
|
+
fs.closeSync(logFd);
|
|
761
812
|
reject(new Error(`Failed to spawn process: ${spawnError instanceof Error ? spawnError.message : String(spawnError)}`));
|
|
762
813
|
return;
|
|
763
814
|
}
|
|
764
815
|
if (!child.pid) {
|
|
765
|
-
|
|
816
|
+
fs.closeSync(logFd);
|
|
766
817
|
reject(new Error('Failed to start process: no PID assigned'));
|
|
767
818
|
return;
|
|
768
819
|
}
|
|
769
820
|
// Handle spawn errors
|
|
770
821
|
child.on('error', (error) => {
|
|
771
|
-
|
|
822
|
+
fs.closeSync(logFd);
|
|
772
823
|
reject(new Error(`Process spawn error: ${error.message}`));
|
|
773
824
|
});
|
|
774
|
-
//
|
|
775
|
-
// The
|
|
825
|
+
// Close the file descriptor in parent process after a short delay
|
|
826
|
+
// The child process will have its own copy
|
|
827
|
+
setTimeout(() => {
|
|
828
|
+
try {
|
|
829
|
+
fs.closeSync(logFd);
|
|
830
|
+
}
|
|
831
|
+
catch {
|
|
832
|
+
// Already closed or error, ignore
|
|
833
|
+
}
|
|
834
|
+
}, 1000);
|
|
776
835
|
// Store process info
|
|
777
836
|
const processInfo = {
|
|
778
837
|
pid: child.pid,
|
|
@@ -784,6 +843,18 @@ function runScriptInBackground(projectPath, projectName, scriptName, args = [],
|
|
|
784
843
|
logFile,
|
|
785
844
|
};
|
|
786
845
|
addProcess(processInfo);
|
|
846
|
+
// Listen for process exit to clean up tracking
|
|
847
|
+
child.on('exit', (code, signal) => {
|
|
848
|
+
// Remove from tracking when process exits
|
|
849
|
+
removeProcess(child.pid);
|
|
850
|
+
// Log exit information
|
|
851
|
+
if (code !== null) {
|
|
852
|
+
fs.appendFileSync(logFile, `\n\n[Process exited with code ${code}]\n`);
|
|
853
|
+
}
|
|
854
|
+
else if (signal !== null) {
|
|
855
|
+
fs.appendFileSync(logFile, `\n\n[Process killed by signal ${signal}]\n`);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
787
858
|
// Unref so parent can exit and process runs independently
|
|
788
859
|
child.unref();
|
|
789
860
|
// Show minimal output
|