projax 0.1.29 → 1.0.1

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.
@@ -36,7 +36,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.detectProjectType = detectProjectType;
37
37
  exports.getProjectScripts = getProjectScripts;
38
38
  exports.runScript = runScript;
39
+ exports.loadProcesses = loadProcesses;
40
+ exports.removeProcess = removeProcess;
39
41
  exports.getProjectProcesses = getProjectProcesses;
42
+ exports.getRunningProcesses = getRunningProcesses;
43
+ exports.stopScript = stopScript;
44
+ exports.stopScriptByPort = stopScriptByPort;
45
+ exports.stopProjectProcesses = stopProjectProcesses;
40
46
  exports.runScriptInBackground = runScriptInBackground;
41
47
  const fs = __importStar(require("fs"));
42
48
  const path = __importStar(require("path"));
@@ -525,6 +531,147 @@ function getProjectProcesses(projectPath) {
525
531
  const processes = loadProcesses();
526
532
  return processes.filter(p => p.projectPath === projectPath);
527
533
  }
534
+ /**
535
+ * Get all running processes
536
+ */
537
+ function getRunningProcesses() {
538
+ return loadProcesses();
539
+ }
540
+ /**
541
+ * Extract URLs from text output
542
+ */
543
+ function extractUrlsFromOutput(output) {
544
+ const urls = [];
545
+ const urlPatterns = [
546
+ /(?:Local|Network):\s*(https?:\/\/[^\s]+)/gi,
547
+ /(?:https?:\/\/localhost:\d+)/gi,
548
+ /(?:https?:\/\/127\.0\.0\.1:\d+)/gi,
549
+ /(?:https?:\/\/0\.0\.0\.0:\d+)/gi,
550
+ /(?:https?:\/\/[^\s:]+:\d+)/gi,
551
+ ];
552
+ for (const pattern of urlPatterns) {
553
+ const matches = output.matchAll(pattern);
554
+ for (const match of matches) {
555
+ const url = match[0] || match[1];
556
+ if (url && !urls.includes(url)) {
557
+ urls.push(url);
558
+ }
559
+ }
560
+ }
561
+ return urls;
562
+ }
563
+ /**
564
+ * Update process with detected URLs
565
+ */
566
+ function updateProcessUrls(pid, urls) {
567
+ const processes = loadProcesses();
568
+ const process = processes.find(p => p.pid === pid);
569
+ if (process) {
570
+ process.detectedUrls = urls;
571
+ saveProcesses(processes);
572
+ }
573
+ }
574
+ /**
575
+ * Stop a script by PID
576
+ */
577
+ async function stopScript(pid) {
578
+ try {
579
+ const processes = loadProcesses();
580
+ const processInfo = processes.find(p => p.pid === pid);
581
+ if (!processInfo) {
582
+ return false;
583
+ }
584
+ // Check if process is still running before trying to kill it
585
+ let processExists = false;
586
+ try {
587
+ if (os.platform() === 'win32') {
588
+ const { exec } = require('child_process');
589
+ const { promisify } = require('util');
590
+ const execAsync = promisify(exec);
591
+ await execAsync(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`);
592
+ processExists = true;
593
+ }
594
+ else {
595
+ const { exec } = require('child_process');
596
+ const { promisify } = require('util');
597
+ const execAsync = promisify(exec);
598
+ await execAsync(`ps -p ${pid} -o pid=`);
599
+ processExists = true;
600
+ }
601
+ }
602
+ catch {
603
+ // Process doesn't exist anymore
604
+ processExists = false;
605
+ }
606
+ // Try to kill the process (cross-platform)
607
+ if (processExists) {
608
+ try {
609
+ if (os.platform() === 'win32') {
610
+ const { exec } = require('child_process');
611
+ const { promisify } = require('util');
612
+ const execAsync = promisify(exec);
613
+ await execAsync(`taskkill /F /PID ${pid}`);
614
+ }
615
+ else {
616
+ const { exec } = require('child_process');
617
+ const { promisify } = require('util');
618
+ const execAsync = promisify(exec);
619
+ await execAsync(`kill -9 ${pid}`);
620
+ }
621
+ }
622
+ catch (error) {
623
+ // Process may have already exited
624
+ console.error(`Error killing process ${pid}:`, error);
625
+ }
626
+ }
627
+ // Remove from tracking regardless of whether kill succeeded
628
+ removeProcess(pid);
629
+ return true;
630
+ }
631
+ catch (error) {
632
+ console.error(`Error stopping script ${pid}:`, error);
633
+ return false;
634
+ }
635
+ }
636
+ /**
637
+ * Stop a script by port (finds process using port and kills it)
638
+ */
639
+ async function stopScriptByPort(port) {
640
+ try {
641
+ const processInfo = await (0, port_utils_1.getProcessOnPort)(port);
642
+ if (!processInfo) {
643
+ return false;
644
+ }
645
+ // Check if this is a tracked process
646
+ const processes = loadProcesses();
647
+ const trackedProcess = processes.find(p => p.pid === processInfo.pid);
648
+ if (trackedProcess) {
649
+ // Use the tracked process removal
650
+ return await stopScript(processInfo.pid);
651
+ }
652
+ else {
653
+ // Kill the process directly
654
+ const killed = await (0, port_utils_1.killProcessOnPort)(port);
655
+ return killed;
656
+ }
657
+ }
658
+ catch (error) {
659
+ return false;
660
+ }
661
+ }
662
+ /**
663
+ * Stop all processes for a project
664
+ */
665
+ async function stopProjectProcesses(projectPath) {
666
+ const processes = getProjectProcesses(projectPath);
667
+ let stopped = 0;
668
+ for (const process of processes) {
669
+ if (await stopScript(process.pid)) {
670
+ stopped++;
671
+ }
672
+ }
673
+ return stopped;
674
+ }
528
675
  /**
529
676
  * Execute a script in the background (minimal logging)
530
677
  */
@@ -626,25 +773,53 @@ function runScriptInBackground(projectPath, projectName, scriptName, args = [],
626
773
  console.log(` Logs: ${logFile}`);
627
774
  console.log(` Command: ${command} ${commandArgs.join(' ')}\n`);
628
775
  // 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
776
+ // But we can check the log file after a short delay for port conflicts and URLs
630
777
  setTimeout(async () => {
631
778
  try {
632
779
  // Wait a bit for process to start and potentially fail
633
780
  await new Promise(resolve => setTimeout(resolve, 2000));
634
781
  if (fs.existsSync(logFile)) {
635
782
  const logContent = fs.readFileSync(logFile, 'utf-8');
783
+ // Check for port conflicts
636
784
  const port = (0, port_utils_1.extractPortFromError)(logContent);
637
785
  if (port) {
638
786
  console.error(`\n⚠️ Port conflict detected in background process: port ${port} is in use`);
639
787
  console.error(` Check log file: ${logFile}`);
640
788
  console.error(` Use: prx <project> <script> --force to auto-resolve port conflicts\n`);
641
789
  }
790
+ // Extract URLs from output
791
+ const urls = extractUrlsFromOutput(logContent);
792
+ if (urls.length > 0) {
793
+ updateProcessUrls(child.pid, urls);
794
+ }
642
795
  }
643
796
  }
644
797
  catch {
645
798
  // Ignore errors checking log file
646
799
  }
647
800
  }, 3000);
801
+ // Also check for URLs from detected ports
802
+ setTimeout(async () => {
803
+ try {
804
+ const db = (0, core_1.getDatabaseManager)();
805
+ const project = db.getProjectByPath(projectPath);
806
+ if (project) {
807
+ const ports = db.getProjectPorts(project.id);
808
+ const urls = [];
809
+ for (const portInfo of ports) {
810
+ if (portInfo.script_name === scriptName) {
811
+ urls.push(`http://localhost:${portInfo.port}`);
812
+ }
813
+ }
814
+ if (urls.length > 0) {
815
+ updateProcessUrls(child.pid, urls);
816
+ }
817
+ }
818
+ }
819
+ catch {
820
+ // Ignore errors
821
+ }
822
+ }, 5000);
648
823
  // Resolve immediately since process is running in background
649
824
  resolve(0);
650
825
  });
package/dist/index.js CHANGED
@@ -42,11 +42,29 @@ const script_runner_1 = require("./script-runner");
42
42
  const port_scanner_1 = require("./port-scanner");
43
43
  // Read version from package.json
44
44
  const packageJson = require('../package.json');
45
+ // ASCII logo for projax
46
+ function displayLogo() {
47
+ return `
48
+ ╔═══════════════════════════════════════╗
49
+ ║ ║
50
+ ║ ██████╗ ██████╗ ██████╗ ██╗ ║
51
+ ║ ██╔══██╗██╔══██╗██╔═══██╗ ██║ ║
52
+ ║ ██████╔╝██████╔╝██║ ██║ ██║ ║
53
+ ║ ██╔═══╝ ██╔══██╗██║ ██║██ ██║ ║
54
+ ║ ██║ ██║ ██║╚██████╔╝╚█████╔╝ ║
55
+ ║ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚════╝ ║
56
+ ║ ║
57
+ ║ Project Management CLI ║
58
+ ║ ║
59
+ ╚═══════════════════════════════════════╝
60
+ `;
61
+ }
45
62
  const program = new commander_1.Command();
46
63
  program
47
64
  .name('prx')
48
65
  .description('Project management dashboard CLI')
49
- .version(packageJson.version);
66
+ .version(packageJson.version)
67
+ .addHelpText('beforeAll', displayLogo());
50
68
  // Add project command
51
69
  program
52
70
  .command('add')
@@ -832,7 +850,7 @@ program
832
850
  // Check if first argument is not a known command
833
851
  (async () => {
834
852
  const args = process.argv.slice(2);
835
- const knownCommands = ['add', 'list', 'scan', 'remove', 'rn', 'rename', 'cd', 'pwd', 'web', 'scripts', 'scan-ports', '--help', '-h', '--version', '-v'];
853
+ const knownCommands = ['add', 'list', 'scan', 'remove', 'rn', 'rename', 'cd', 'pwd', 'web', 'scripts', 'scan-ports', '--help', '-h', '--version', '-V'];
836
854
  // If we have at least 1 argument and first is not a known command, treat as project identifier
837
855
  if (args.length >= 1 && !knownCommands.includes(args[0])) {
838
856
  const projectIdentifier = args[0];
@@ -967,5 +985,6 @@ program
967
985
  }
968
986
  }
969
987
  // If we get here, proceed with normal command parsing
988
+ // Don't show logo twice - it's already in addHelpText
970
989
  program.parse();
971
990
  })();
@@ -29,11 +29,36 @@ export interface BackgroundProcess {
29
29
  command: string;
30
30
  startedAt: number;
31
31
  logFile: string;
32
+ detectedUrls?: string[];
32
33
  }
34
+ /**
35
+ * Load running processes from disk
36
+ */
37
+ export declare function loadProcesses(): BackgroundProcess[];
38
+ /**
39
+ * Remove a process from tracking by PID
40
+ */
41
+ export declare function removeProcess(pid: number): void;
33
42
  /**
34
43
  * Get all running processes for a project
35
44
  */
36
45
  export declare function getProjectProcesses(projectPath: string): BackgroundProcess[];
46
+ /**
47
+ * Get all running processes
48
+ */
49
+ export declare function getRunningProcesses(): BackgroundProcess[];
50
+ /**
51
+ * Stop a script by PID
52
+ */
53
+ export declare function stopScript(pid: number): Promise<boolean>;
54
+ /**
55
+ * Stop a script by port (finds process using port and kills it)
56
+ */
57
+ export declare function stopScriptByPort(port: number): Promise<boolean>;
58
+ /**
59
+ * Stop all processes for a project
60
+ */
61
+ export declare function stopProjectProcesses(projectPath: string): Promise<number>;
37
62
  /**
38
63
  * Execute a script in the background (minimal logging)
39
64
  */
@@ -36,7 +36,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.detectProjectType = detectProjectType;
37
37
  exports.getProjectScripts = getProjectScripts;
38
38
  exports.runScript = runScript;
39
+ exports.loadProcesses = loadProcesses;
40
+ exports.removeProcess = removeProcess;
39
41
  exports.getProjectProcesses = getProjectProcesses;
42
+ exports.getRunningProcesses = getRunningProcesses;
43
+ exports.stopScript = stopScript;
44
+ exports.stopScriptByPort = stopScriptByPort;
45
+ exports.stopProjectProcesses = stopProjectProcesses;
40
46
  exports.runScriptInBackground = runScriptInBackground;
41
47
  const fs = __importStar(require("fs"));
42
48
  const path = __importStar(require("path"));
@@ -525,6 +531,147 @@ function getProjectProcesses(projectPath) {
525
531
  const processes = loadProcesses();
526
532
  return processes.filter(p => p.projectPath === projectPath);
527
533
  }
534
+ /**
535
+ * Get all running processes
536
+ */
537
+ function getRunningProcesses() {
538
+ return loadProcesses();
539
+ }
540
+ /**
541
+ * Extract URLs from text output
542
+ */
543
+ function extractUrlsFromOutput(output) {
544
+ const urls = [];
545
+ const urlPatterns = [
546
+ /(?:Local|Network):\s*(https?:\/\/[^\s]+)/gi,
547
+ /(?:https?:\/\/localhost:\d+)/gi,
548
+ /(?:https?:\/\/127\.0\.0\.1:\d+)/gi,
549
+ /(?:https?:\/\/0\.0\.0\.0:\d+)/gi,
550
+ /(?:https?:\/\/[^\s:]+:\d+)/gi,
551
+ ];
552
+ for (const pattern of urlPatterns) {
553
+ const matches = output.matchAll(pattern);
554
+ for (const match of matches) {
555
+ const url = match[0] || match[1];
556
+ if (url && !urls.includes(url)) {
557
+ urls.push(url);
558
+ }
559
+ }
560
+ }
561
+ return urls;
562
+ }
563
+ /**
564
+ * Update process with detected URLs
565
+ */
566
+ function updateProcessUrls(pid, urls) {
567
+ const processes = loadProcesses();
568
+ const process = processes.find(p => p.pid === pid);
569
+ if (process) {
570
+ process.detectedUrls = urls;
571
+ saveProcesses(processes);
572
+ }
573
+ }
574
+ /**
575
+ * Stop a script by PID
576
+ */
577
+ async function stopScript(pid) {
578
+ try {
579
+ const processes = loadProcesses();
580
+ const processInfo = processes.find(p => p.pid === pid);
581
+ if (!processInfo) {
582
+ return false;
583
+ }
584
+ // Check if process is still running before trying to kill it
585
+ let processExists = false;
586
+ try {
587
+ if (os.platform() === 'win32') {
588
+ const { exec } = require('child_process');
589
+ const { promisify } = require('util');
590
+ const execAsync = promisify(exec);
591
+ await execAsync(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`);
592
+ processExists = true;
593
+ }
594
+ else {
595
+ const { exec } = require('child_process');
596
+ const { promisify } = require('util');
597
+ const execAsync = promisify(exec);
598
+ await execAsync(`ps -p ${pid} -o pid=`);
599
+ processExists = true;
600
+ }
601
+ }
602
+ catch {
603
+ // Process doesn't exist anymore
604
+ processExists = false;
605
+ }
606
+ // Try to kill the process (cross-platform)
607
+ if (processExists) {
608
+ try {
609
+ if (os.platform() === 'win32') {
610
+ const { exec } = require('child_process');
611
+ const { promisify } = require('util');
612
+ const execAsync = promisify(exec);
613
+ await execAsync(`taskkill /F /PID ${pid}`);
614
+ }
615
+ else {
616
+ const { exec } = require('child_process');
617
+ const { promisify } = require('util');
618
+ const execAsync = promisify(exec);
619
+ await execAsync(`kill -9 ${pid}`);
620
+ }
621
+ }
622
+ catch (error) {
623
+ // Process may have already exited
624
+ console.error(`Error killing process ${pid}:`, error);
625
+ }
626
+ }
627
+ // Remove from tracking regardless of whether kill succeeded
628
+ removeProcess(pid);
629
+ return true;
630
+ }
631
+ catch (error) {
632
+ console.error(`Error stopping script ${pid}:`, error);
633
+ return false;
634
+ }
635
+ }
636
+ /**
637
+ * Stop a script by port (finds process using port and kills it)
638
+ */
639
+ async function stopScriptByPort(port) {
640
+ try {
641
+ const processInfo = await (0, port_utils_1.getProcessOnPort)(port);
642
+ if (!processInfo) {
643
+ return false;
644
+ }
645
+ // Check if this is a tracked process
646
+ const processes = loadProcesses();
647
+ const trackedProcess = processes.find(p => p.pid === processInfo.pid);
648
+ if (trackedProcess) {
649
+ // Use the tracked process removal
650
+ return await stopScript(processInfo.pid);
651
+ }
652
+ else {
653
+ // Kill the process directly
654
+ const killed = await (0, port_utils_1.killProcessOnPort)(port);
655
+ return killed;
656
+ }
657
+ }
658
+ catch (error) {
659
+ return false;
660
+ }
661
+ }
662
+ /**
663
+ * Stop all processes for a project
664
+ */
665
+ async function stopProjectProcesses(projectPath) {
666
+ const processes = getProjectProcesses(projectPath);
667
+ let stopped = 0;
668
+ for (const process of processes) {
669
+ if (await stopScript(process.pid)) {
670
+ stopped++;
671
+ }
672
+ }
673
+ return stopped;
674
+ }
528
675
  /**
529
676
  * Execute a script in the background (minimal logging)
530
677
  */
@@ -626,25 +773,53 @@ function runScriptInBackground(projectPath, projectName, scriptName, args = [],
626
773
  console.log(` Logs: ${logFile}`);
627
774
  console.log(` Command: ${command} ${commandArgs.join(' ')}\n`);
628
775
  // 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
776
+ // But we can check the log file after a short delay for port conflicts and URLs
630
777
  setTimeout(async () => {
631
778
  try {
632
779
  // Wait a bit for process to start and potentially fail
633
780
  await new Promise(resolve => setTimeout(resolve, 2000));
634
781
  if (fs.existsSync(logFile)) {
635
782
  const logContent = fs.readFileSync(logFile, 'utf-8');
783
+ // Check for port conflicts
636
784
  const port = (0, port_utils_1.extractPortFromError)(logContent);
637
785
  if (port) {
638
786
  console.error(`\n⚠️ Port conflict detected in background process: port ${port} is in use`);
639
787
  console.error(` Check log file: ${logFile}`);
640
788
  console.error(` Use: prx <project> <script> --force to auto-resolve port conflicts\n`);
641
789
  }
790
+ // Extract URLs from output
791
+ const urls = extractUrlsFromOutput(logContent);
792
+ if (urls.length > 0) {
793
+ updateProcessUrls(child.pid, urls);
794
+ }
642
795
  }
643
796
  }
644
797
  catch {
645
798
  // Ignore errors checking log file
646
799
  }
647
800
  }, 3000);
801
+ // Also check for URLs from detected ports
802
+ setTimeout(async () => {
803
+ try {
804
+ const db = (0, core_1.getDatabaseManager)();
805
+ const project = db.getProjectByPath(projectPath);
806
+ if (project) {
807
+ const ports = db.getProjectPorts(project.id);
808
+ const urls = [];
809
+ for (const portInfo of ports) {
810
+ if (portInfo.script_name === scriptName) {
811
+ urls.push(`http://localhost:${portInfo.port}`);
812
+ }
813
+ }
814
+ if (urls.length > 0) {
815
+ updateProcessUrls(child.pid, urls);
816
+ }
817
+ }
818
+ }
819
+ catch {
820
+ // Ignore errors
821
+ }
822
+ }, 5000);
648
823
  // Resolve immediately since process is running in background
649
824
  resolve(0);
650
825
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projax",
3
- "version": "0.1.29",
3
+ "version": "1.0.1",
4
4
  "description": "CLI tool for managing local development projects",
5
5
  "main": "dist/index.js",
6
6
  "bin": {