2024โ€ขTools
PROJAX
โ† Back

PROJAX

Cross-platform project management dashboard for managing local development projects across different tech stacks.

Preview

Your entire Developer folder at a glance

Key Features

๐Ÿ“‹

Project List

See all your projects at a glance โ€” what's running, what has uncommitted changes, what port each is using.

๐Ÿš€

Smart Port Allocation

Automatically finds available ports. Never fight with 'port already in use' again.

๐Ÿ”

Git Status

Branch name, uncommitted files, ahead/behind remote โ€” without cd-ing into each folder.

๐Ÿ–ฅ๏ธ

Multiple Interfaces

CLI for quick queries, TUI for browsing, desktop app for always-on monitoring, VS Code extension for IDE integration.

โšก

Zero Config

Point it at your Developer folder and it figures everything out. No setup required.

๐Ÿงน

Cleanup Tools

Find and delete old node_modules to reclaim disk space.

I have too many projects. Like, way too many.

My Developer folder is a graveyard of good intentions. Half-finished ideas, client work from 2019, experiments that made sense at 2am, and at least three folders called "test" that I'm afraid to delete.

At last count: 50+ projects. Most of them have some combination of uncommitted changes, running dev servers, or dependencies that haven't been updated since the Obama administration.

The Breaking Point

I was working on a project when my port got stolen. Not by a hacker โ€” by MYSELF. I had a dev server running on port 3000 from some project I'd forgotten about, and when I tried to start my current project, it just failed silently.

Fifteen minutes of debugging later: "Oh, right, I left that other thing running from Tuesday."

This happened at least once a week. I needed a system.

What Projax Does

Think of it as Mission Control for your local development environment.

projax list shows every project, what's running, what has uncommitted changes, and what port each thing is using. At a glance, I can see:

  • โ€ขqortr โ€” Running on port 3000, 3 uncommitted files
  • โ€ขcrativo.xyz โ€” Running on port 3001, clean
  • โ€ขthat-thing-from-tuesday โ€” Not running, but has 47 uncommitted files (oops)

projax run my-project starts the dev server with smart port allocation. If 3000 is taken, it finds the next available port and remembers it.

projax status gives me the big picture: total projects, how many are dirty, what's consumi...

Built With

Node.jsElectronExpress.jsCLI

Impact

npm Downloads

8,700+

Monthly

1,750+

Under the Hood

A peek at the implementation โ€” the kind of code that powers PROJAX.

PortDetector.tstypescript
1// The port detection that handles 90% of cases
2// (The other 10% are crimes against configuration)
3
4interface PortInfo {
5  configured: number[];    // Explicitly set in config
6  conventional: number[];  // Framework defaults
7  running: number[];       // Currently bound
8}
9
10async function detectPorts(projectPath: string): Promise<PortInfo> {
11  const pkg = await readPackageJson(projectPath);
12  const scripts = pkg?.scripts || {};
13  
14  const configured: Set<number> = new Set();
15  
16  // 1. Check scripts for --port arguments
17  for (const script of Object.values(scripts)) {
18    const match = script.match(/--port[=s]+(d+)/i);
19    if (match) configured.add(parseInt(match[1]));
20  }
21  
22  // 2. Check config files
23  const configPorts = await Promise.all([
24    extractFromViteConfig(projectPath),
25    extractFromNextConfig(projectPath),
26    extractFromWebpackConfig(projectPath),
27  ]);
28  configPorts.flat().forEach(p => configured.add(p));
29  
30  // 3. Check .env files
31  const envPorts = await extractFromEnvFiles(projectPath);
32  envPorts.forEach(p => configured.add(p));
33  
34  // 4. Apply convention if nothing configured
35  let conventional: number[] = [];
36  if (configured.size === 0) {
37    conventional = inferFromFramework(scripts);
38  }
39  
40  // 5. Check what's actually running
41  const allPorts = [...configured, ...conventional];
42  const running = await checkRunningPorts(allPorts, projectPath);
43  
44  return {
45    configured: [...configured],
46    conventional,
47    running
48  };
49}
50
51function inferFromFramework(scripts: Record<string, string>): number[] {
52  const text = JSON.stringify(scripts).toLowerCase();
53  
54  if (text.includes('vite')) return [5173];
55  if (text.includes('next')) return [3000];
56  if (text.includes('nuxt')) return [3000];
57  if (text.includes('angular')) return [4200];
58  if (text.includes('gatsby')) return [8000];
59  if (text.includes('astro')) return [4321];
60  if (text.includes('remix')) return [3000];
61  
62  // Generic Node.js
63  if (text.includes('node ') || text.includes('nodemon')) return [3000];
64  
65  return [];
66}
67
68async function checkRunningPorts(ports: number[], projectPath: string): Promise<number[]> {
69  const running: number[] = [];
70  
71  for (const port of ports) {
72    try {
73      // lsof tells us what's using the port
74      const { stdout } = await exec(`lsof -i :${port} -t`);
75      if (stdout.trim()) {
76        // Check if it's OUR project
77        const pid = parseInt(stdout.trim().split('\n')[0]);
78        const cwd = await getProcessCwd(pid);
79        if (cwd.includes(projectPath)) {
80          running.push(port);
81        }
82      }
83    } catch {
84      // Port not in use
85    }
86  }
87  
88  return running;
89}

Discussion

๐Ÿ’ฌ Be nice, or be funny. Preferably both.