Skip to content

Conversation

@bcotrim
Copy link
Contributor

@bcotrim bcotrim commented Nov 6, 2025

Related issues

  • Fixes #

Proposed Changes

Testing Instructions

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

@github-actions
Copy link

github-actions bot commented Nov 6, 2025

📊 Performance Test Results

Comparing 8da23f3 vs trunk

site-editor

Metric trunk 8da23f3 Diff Change
load 12263.00 ms 9522.00 ms -2741.00 ms 🟢 -22.4%

site-startup

Metric trunk 8da23f3 Diff Change
siteCreation 13121.00 ms 16091.00 ms +2970.00 ms 🔴 22.6%
siteStartup 4935.00 ms 4921.00 ms -14.00 ms 🟢 -0.3%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change

Copy link
Contributor

@fredrikekelund fredrikekelund left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I collected some thoughts and questions as I went over the PR.

The biggest question, though, is whether we take this pm2 approach or something more homegrown. I have some thoughts there, but I'd like to hear yours, too, after working on this change. Let's discuss this in a huddle later 🙂

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we should reconsider how CLI dependencies are handled now and move everything that only the CLI depends on to this package.json? Or is pm2 still an edge case?

EDIT: I realize now that Vite copies the entire node_modules folder to the build directory, so we should probably NOT move all CLI dependencies here, since that would make the build bundle unnecessarily large.

Comment on lines +10 to +17
const possiblePaths: string[] = [
path.join( __dirname, 'node_modules', 'pm2' ),
path.join( path.dirname( __dirname ), 'node_modules', 'pm2' ),
path.join( __dirname, '..', 'node_modules', 'pm2' ),
path.join( __dirname, '..', '..', 'node_modules', 'pm2' ),
path.join( __dirname, '..', '..', '..', 'node_modules', 'pm2' ),
path.resolve( process.cwd(), 'node_modules', 'pm2' ),
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plenty of variation here 🤔 At first, I thought that it would be convenient if we could continue shipping the CLI as a single JS file to be executed by Electron's bundled node.js runtime, but then I realized that the PHP-WASM binaries will need to be handled similarly anyway, so maybe this is something we can live with after all…

await ensureDaemonRunning();

return new Promise( ( resolve, reject ) => {
const processConfig: pm2.StartOptions = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const processConfig: pm2.StartOptions = {
const processConfig: Pm2StartOptions = {

Doing this and adding an import statement like so at the top seems to work.

import { StartOptions as Pm2StartOptions } from 'pm2';

@bcotrim bcotrim self-assigned this Nov 7, 2025
@bcotrim bcotrim force-pushed the add-pm2-cli-commands branch from e4e05b9 to 6d54776 Compare November 7, 2025 14:15
@bcotrim bcotrim force-pushed the add-pm2-cli-commands branch from 6d54776 to fa322c3 Compare November 7, 2025 22:52
@claude
Copy link

claude bot commented Nov 7, 2025

Claude finished @bcotrim's task —— View job


Code Review: Add PM2 CLI Commands

I've completed my review of this PR. This introduces PM2-based process management for CLI sites with a persistent proxy daemon. Here's my comprehensive feedback:


Summary

This PR adds PM2 daemon management to enable CLI-based site management with persistent proxy servers for custom domains. The implementation moves proxy server logic from the main Electron process to a PM2-managed CLI daemon that can run independently.

Key Changes:

  • Adds PM2 process manager integration via cli/lib/pm2-manager.ts
  • Creates a standalone proxy daemon (cli/proxy-daemon.ts) for custom domain routing
  • Moves certificate-manager.ts from src/lib to cli/lib to share code
  • Adds CLI command studio site start to launch the proxy with sudo
  • Introduces separate cli/package.json for PM2 and http-proxy dependencies

Architecture Concerns

1. Code Duplication Between Electron and CLI Proxy Servers

Issue: The proxy server logic is now duplicated:

  • Original: src/lib/proxy-server.ts (279 lines) - Used by Electron app
  • New: cli/lib/proxy-server.ts (397 lines) - Used by PM2 CLI daemon

Key Differences:

  • Electron version: Uses SiteServer.get() to check if sites are running, integrates with app state, includes Sentry error tracking
  • CLI version: Reads from appdata-v1.json file with caching, includes file watcher, simpler error handling
  • Certificate handling: Electron version uses in-memory certs from SiteServer; CLI version generates on-the-fly via generateSiteCertificate()

Recommendation: This duplication creates maintenance burden. Consider:

  1. Extract shared proxy logic into common/lib/proxy-server-shared.ts
  2. Create thin wrappers in src/ and cli/ that provide site lookup strategies
  3. Or document explicitly why two implementations are needed (if this is intentional for the CLI beta)

Reference: cli/lib/proxy-server.ts:1-397 vs src/lib/proxy-server.ts:1-279


2. PM2 Module Resolution Complexity

Issue: The resolvePm2() function tries 6 different paths to find the PM2 module (cli/lib/pm2-manager.ts:6-35). This suggests uncertainty about bundling strategy.

Problems:

  • Fragile path resolution that may break in different environments
  • No clear documentation of which path is expected in production vs development
  • Falls back through multiple directories without clear reasoning

Recommendation:

  • Choose ONE bundling approach and stick with it
  • The vite.cli.config.ts already copies cli/node_modules to dist, so PM2 should always be at a predictable location
  • Simplify to just 1-2 paths: current directory's node_modules, and the copied cli/node_modules
  • Add clear comments explaining which path is for dev vs production

Reference: cli/lib/pm2-manager.ts:6-35


3. Type Safety Issues

Issue: Using pm2.StartOptions inline without proper imports (cli/lib/pm2-manager.ts:161, 286):

const processConfig: pm2.StartOptions = {  // Type used but not imported

Reviewer fredrikekelund already noted this at cli/lib/pm2-manager.ts:286.

Fix:

import { StartOptions as Pm2StartOptions } from 'pm2';

const processConfig: Pm2StartOptions = {

Reference: cli/lib/pm2-manager.ts:161, 286


4. Security: Running Proxy as Root

Issue: The proxy daemon explicitly runs as root (cli/lib/pm2-manager.ts:296):

uid: 0, // Run as root to bind to ports 80 and 443

Concerns:

  • Running user code (proxy server) with root privileges increases attack surface
  • If the proxy has vulnerabilities, entire system is at risk
  • The proxy reads user-provided site configurations and generates certificates

Recommendations:

  1. Document security implications clearly in comments
  2. Consider alternative approaches:
    • Use authbind on Linux to bind low ports without root
    • Use port forwarding (iptables) from 80/443 to high ports
    • Warn users about security tradeoffs
  3. Minimize code running as root - perhaps only the port binding portion

Reference: cli/lib/pm2-manager.ts:296


5. Certificate Manager Path Handling

Issue: The CLI certificate manager falls back to calculating appdata path when STUDIO_APPDATA_PATH env var is not set (cli/lib/proxy-server.ts:54-75). However, the comment says "this is necessary because when running as root, we can't reliably calculate the user's appdata path."

Concern: If running as root and the env var is NOT set, the fallback will incorrectly use root's home directory (/var/root on macOS), not the user's home.

Recommendation:

  • Make STUDIO_APPDATA_PATH required when running as root
  • Throw a clear error if it's missing rather than silently using wrong path
  • Add validation that the path exists and is readable

Reference: cli/lib/proxy-server.ts:54-75


Code Quality Issues

6. Inconsistent Error Handling

CLI proxy-server.ts:

  • Errors are logged with console.error() but execution continues
  • File watcher setup failures are non-fatal (line 349)
  • Site lookup errors return null but don't propagate (line 110)

Recommendation:

  • Define clear error handling strategy: which errors are fatal vs recoverable?
  • Consider using proper logging library instead of console.log
  • Add structured logging for debugging production issues

Reference: cli/lib/proxy-server.ts:110, 349


7. Cache Invalidation Logic

Issue: The cache invalidation is overly aggressive (cli/lib/proxy-server.ts:118-121):

function invalidateCache() {
	sitesCache = [];
	lastLoadTime = 0;
}

The file watcher calls invalidateCache() on every change, but there's also a 5-second TTL. This means:

  • File changes invalidate immediately
  • But there's still a 5-second cache between changes
  • The dual mechanism is confusing

Recommendation:

  • Either use file watcher OR TTL cache, not both
  • If using both, document why (e.g., "TTL for performance, watcher for correctness")

Reference: cli/lib/proxy-server.ts:38, 118-121, 342-346


8. Missing Error Context in PM2 Manager

Issue: Many PM2 operations fail silently or with generic errors:

pm2.start( processConfig, ( error, apps ) => {
	disconnect();
	if ( error ) {
		reject( error );  // No context about what was starting
		return;
	}

Recommendation: Wrap errors with context:

if ( error ) {
	reject( new Error( `Failed to start PM2 process "${options.name}": ${error.message}` ) );
	return;
}

Reference: cli/lib/pm2-manager.ts:173-178


9. Health Check Endpoint Returns Internal State

Issue: The health check exposes internal state (cli/lib/proxy-server.ts:142-152):

{
	status: 'ok',
	http: isHttpProxyRunning,
	https: isHttpsProxyRunning,
	timestamp: Date.now(),
}

Concern: This endpoint is accessible to any site since it's checked before host validation. Could leak information.

Recommendation:

  • Limit health check to localhost only
  • Or remove internal state details, just return { status: 'ok' }

Reference: cli/lib/proxy-server.ts:142-152, 163-164


Testing & Documentation Concerns

10. Missing Tests

Issue: No tests added for:

  • PM2 manager functions
  • Proxy server routing logic
  • Certificate generation in CLI context
  • Sudo privilege checking

Recommendation: Add at least unit tests for:

  • getSiteByHost() - critical routing logic
  • PM2 process lifecycle management
  • Certificate path handling when running as root

11. PR Description is Empty

Issue: The PR description has placeholders but no actual content:

## Related issues
- Fixes #

## Proposed Changes
-

## Testing Instructions
-

Recommendation: Fill in:

  • What problem does this solve?
  • Why PM2 vs other approaches? (Address fredrikekelund's question)
  • How to test the proxy functionality?
  • Security considerations for running as root

Performance Concerns

12. Site Creation Regression

Issue: Performance tests show site creation time increased by 22.6% (16091ms vs 13121ms).

Possible causes:

  • Build configuration changes copying extra node_modules
  • PM2 initialization overhead
  • Certificate manager relocation

Recommendation: Investigate why site creation is slower and document findings.


Build Configuration

13. CLI Node Modules Bundling Strategy

Issue: Reviewer fredrikekelund asked about moving CLI dependencies to cli/package.json (cli/package.json:?). The vite config copies the entire cli/node_modules folder to dist (vite.cli.config.ts:23-34).

Current approach:

  • PM2 and http-proxy are in cli/package.json
  • These get installed to cli/node_modules/
  • Vite copies entire cli/node_modules/ to dist/cli/node_modules/
  • CLI is marked as external in rollup config

Concern: This is inconsistent with the rest of the app's bundling. Why treat PM2 specially?

Questions to answer:

  1. Can PM2 be bundled like other dependencies?
  2. If not, why? (Native modules? Dynamic requires?)
  3. Should this pattern extend to other CLI dependencies?

Reference: vite.cli.config.ts:23-34, cli/package.json:10-13


Recommendations Summary

High Priority:

  1. ✅ Fix TypeScript type imports (easy fix)
  2. 🔴 Address code duplication between Electron and CLI proxy servers
  3. 🔴 Document security implications of running as root
  4. 🔴 Make STUDIO_APPDATA_PATH required when running as root
  5. 🔴 Investigate site creation performance regression

Medium Priority:
6. 🟡 Simplify PM2 module resolution logic
7. 🟡 Improve error handling and add context to error messages
8. 🟡 Add tests for critical path logic
9. 🟡 Fill in PR description with rationale and testing instructions

Low Priority:
10. 🟢 Simplify cache invalidation (choose file watcher OR TTL)
11. 🟢 Secure or simplify health check endpoint
12. 🟢 Decide on CLI dependency bundling strategy going forward


Architecture Documentation Updates

Should CLAUDE.md be updated? Yes, but after deciding on the approach.

This PR introduces a significant new architectural pattern:

  • PM2-based process management for CLI sites (vs Electron's child_process for app sites)
  • Dual proxy implementations (Electron in-process vs PM2 daemon)
  • CLI-specific dependencies with separate package.json

However, fredrikekelund's review comment suggests the team is still deciding between "this pm2 approach or something more homegrown."

Recommendation:

  • Don't update CLAUDE.md until the architectural approach is finalized
  • Once decided, add a new section on "CLI Site Management" explaining:
    • How PM2 manages CLI site processes
    • Differences between Electron app sites and CLI sites
    • Why there are two proxy server implementations
    • Security model for running proxy as root

Questions for Discussion

Based on reviewer feedback:

  1. PM2 vs Homegrown? What led to choosing PM2? What are the tradeoffs? (@fredrikekelund mentioned discussing in huddle)

  2. Dependency Management? Should more CLI deps move to cli/package.json, or is PM2 a special case? (@fredrikekelund's comment on cli/package.json)

  3. Code Sharing Strategy? Is the proxy duplication intentional (CLI beta separate from app), or should they share code?

  4. Security Model? Are users aware they're running a persistent root daemon? Is there a way to avoid this?


@claude
Copy link

claude bot commented Nov 7, 2025

Claude finished @bcotrim's task —— View job


Code Review Complete ✅

I've performed a comprehensive review of this PR that adds PM2-based CLI commands for managing WordPress sites. Below are my findings organized by priority and category.

Tasks:

  • Read and analyze all changed files
  • Review cross-platform compatibility (macOS/Windows)
  • Assess code quality and architecture
  • Check security concerns (OWASP Top 10, Electron security)
  • Evaluate performance implications
  • Review testing coverage
  • Check documentation updates needed

🚨 Critical Issues

1. Windows Compatibility is Broken (cli/lib/pm2-manager.ts:296)

uid: 0, // Run as root to bind to ports 80 and 443

Problem: The uid option doesn't work on Windows and will cause PM2 to fail. Windows doesn't use Unix-style UIDs.

Impact: The entire proxy feature will be non-functional on Windows.

Recommendation: Use platform detection:

const processConfig: pm2.StartOptions = {
  name: PROXY_PROCESS_NAME,
  script: scriptPath,
  // ... other options
  ...(process.platform !== 'win32' && { uid: 0 }), // Only on Unix-like systems
};

2. Windows Admin Privileges Not Implemented (cli/lib/sudo-exec.ts:18-20)

// On Windows, we'd need to check for admin rights
// For now, we'll return false and handle Windows separately
// TODO: Implement Windows admin check
return false;

Problem: The proxy start command will never work on Windows because it always returns false for admin check.

Impact: Windows users cannot use the proxy feature at all.

Recommendation: Implement Windows admin detection using net session or similar:

if (process.platform === 'win32') {
  try {
    execSync('net session', { stdio: 'ignore' });
    return true;
  } catch {
    return false;
  }
}

3. PM2 Socket Path Assumption (cli/lib/pm2-manager.ts:105-108)

const homeDir = os.homedir();
const pm2Dir = path.join(homeDir, '.pm2');
const rpcSocket = path.join(pm2Dir, 'rpc.sock');

Problem: On Windows, PM2 uses named pipes, not Unix sockets (.sock files don't exist on Windows). This isDaemonRunning() check will always fail.

Recommendation: Use PM2's built-in connection check or handle platform differences in socket detection.


⚠️ High Priority Issues

4. Missing Error Handling for File System Operations

Multiple locations perform synchronous file operations without proper error handling:

  • cli/lib/proxy-server.ts:84 - readFileSync without try-catch in a loop
  • cli/lib/certificate-manager.ts:140-143 - File write operations without error handling for disk full scenarios

Recommendation: Wrap all file I/O in proper try-catch blocks with meaningful error messages.

5. Security: Running as Root/Admin is Risky (cli/lib/pm2-manager.ts:296)

Running the entire proxy daemon with uid: 0 (root) is a significant security risk. If there's a vulnerability in the proxy code, an attacker has root access.

Recommendation:

  • Consider using a capability-based approach (CAP_NET_BIND_SERVICE on Linux)
  • Or use port forwarding from high ports (e.g., 8080 → 80)
  • Document the security implications clearly
  • Consider making HTTPS optional by default

6. Certificate Trust Operation Uses sudo-prompt (cli/lib/certificate-manager.ts:202-214)

The @vscode/sudo-prompt library is being used to add certificates to the system trust store. This will prompt users for their password.

Concerns:

  • Users might not understand why they're being prompted for a password
  • This happens automatically on first certificate generation
  • No user consent flow before requesting elevated privileges

Recommendation: Add a clear user consent flow before requesting sudo access for certificate trust.

7. Race Condition in Cache Invalidation (cli/lib/proxy-server.ts:99-113)

The cache TTL pattern has a race condition: if multiple requests come in simultaneously after cache expiry, they'll all try to read the file concurrently.

Recommendation: Implement a proper cache with locking or use a debounced reload mechanism.


🔶 Medium Priority Issues

8. Type Safety Issues

cli/lib/pm2-manager.ts:161 - Type casting without validation:

const processConfig: pm2.StartOptions = { ... }

The interface StartOptions is locally defined but PM2's types are cast directly. As fredrikekelund mentioned in the review comments, this can be improved by importing the proper type:

import { StartOptions as Pm2StartOptions } from 'pm2';

9. Hard-coded Port Numbers (cli/lib/proxy-server.ts:222, 276)

Ports 80 and 443 are hard-coded. Consider:

  • Making these configurable for development/testing
  • What happens if another service is using these ports?
  • No error recovery if ports are in use

10. PM2 Module Resolution Complexity (cli/lib/pm2-manager.ts:6-35)

The resolvePm2() function tries 6 different paths to find the pm2 module. This is fragile and suggests the build/packaging strategy might need refinement.

Recommendation: Ensure PM2 is properly bundled or use a more reliable resolution strategy.

11. Missing Input Validation

cli/lib/proxy-server.ts:126-136 - getSiteByHost() doesn't validate domain format before using domainToASCII(). Malformed domains could cause errors.

Recommendation: Add domain validation and sanitization.

12. Memory Leak Potential (cli/lib/proxy-server.ts:40)

const proxy = httpProxy.createProxyServer();

The proxy object is created globally but never cleaned up. Error handlers are attached but the proxy itself isn't managed properly.

Recommendation: Implement proper cleanup in stopProxyServers().

13. Process Kill Signal Not Cross-Platform (cli/lib/pm2-manager.ts:115)

process.kill(pid, 0); // Signal 0 to check if process exists

While signal 0 works on Unix-like systems, this pattern may behave differently on Windows.


📝 Code Quality Issues

14. Inconsistent Disconnect Pattern

In pm2-manager.ts, disconnect() is called in almost every callback after operations complete. This makes the connection lifecycle confusing. Each operation connects, does work, then disconnects.

Recommendation: Consider whether PM2 daemon should stay connected for the lifetime of the CLI process, or document why the connect/disconnect pattern is necessary.

15. Magic Numbers Without Constants

  • cli/lib/proxy-server.ts:38 - CACHE_TTL = 5000 - Why 5 seconds?
  • cli/lib/pm2-manager.ts:293 - restart_delay: 3000 - Why 3 seconds?
  • cli/lib/certificate-manager.ts:67-68 - Certificate validity days

Recommendation: Document the rationale for these values or extract to well-named constants.

16. Console.log Usage Instead of Proper Logging

Many files use console.log() and console.error() directly instead of using the Logger class that exists elsewhere in the codebase.

Files affected:

  • cli/lib/proxy-server.ts (many instances)
  • cli/lib/pm2-manager.ts:335
  • cli/proxy-daemon.ts:18

Recommendation: Use a consistent logging approach, possibly the Logger class from cli/logger.

17. Missing JSDoc Comments

Several exported functions lack documentation:

  • cli/lib/pm2-manager.ts - Most functions have minimal comments
  • cli/lib/proxy-server.ts:startProxyServers() - No param/return docs

Recommendation: Add comprehensive JSDoc comments for all public APIs.

18. Inconsistent Async Patterns

Some functions use Promise wrappers around callbacks (good), but isDaemonRunning() is synchronous while related functions are async. This inconsistency makes the API harder to understand.


🧪 Testing Issues

19. Zero Test Coverage

Critical Gap: No tests were added for any of the new functionality:

  • No tests for pm2-manager.ts
  • No tests for proxy-server.ts
  • No tests for sudo-exec.ts
  • No tests for cli/commands/site/start.ts

Recommendation: Add comprehensive unit tests:

// Examples needed:
describe('pm2-manager', () => {
  describe('isDaemonRunning', () => {
    it('returns false when PM2 daemon is not running', () => { ... });
    it('returns true when PM2 daemon is running', () => { ... });
    it('handles missing .pm2 directory', () => { ... });
  });
  
  describe('startProxyProcess', () => {
    it('starts proxy with correct configuration', async () => { ... });
    it('passes environment variables correctly', async () => { ... });
    it('handles PM2 start failures', async () => { ... });
  });
});

20. No E2E Tests

The proxy functionality requires privileged port binding and certificate generation. These should have E2E tests that verify the full flow.

Recommendation: Add Playwright E2E tests that:

  • Start the proxy with proper permissions
  • Verify HTTP/HTTPS routing works
  • Test certificate generation
  • Verify graceful shutdown

📚 Documentation Issues

21. Missing CLAUDE.md Updates

The CLAUDE.md file should be updated to document:

  • The new PM2 integration and its implications
  • How the proxy server architecture works
  • Development workflow for testing proxy features
  • Platform-specific considerations

22. No README or User Documentation

Users need documentation on:

  • What the proxy feature does
  • Why it requires sudo/admin privileges
  • Security implications of running as root
  • How to troubleshoot proxy issues
  • Windows-specific setup instructions

23. PR Description is Empty

The PR description has placeholders but no actual content:

  • No related issues referenced
  • No proposed changes explained
  • No testing instructions provided

⚡ Performance Concerns

24. 22.6% Regression in Site Creation Time

The performance metrics show:

  • siteCreation: 13121ms → 16091ms (+2970ms, +22.6%)

This is a significant regression. The added ~3 seconds to site creation is substantial.

Investigation needed:

  • Is this caused by PM2 daemon startup?
  • Is it the certificate generation?
  • Does this happen on every site creation or just the first?

Recommendation: Profile the site creation flow to identify the bottleneck and optimize it.

25. Certificate Generation on Every Request (cli/lib/proxy-server.ts:255)

While there's disk caching, the generateSiteCertificate() function is called synchronously in the SNI callback for HTTPS requests. This could cause delays on first HTTPS request per domain.

Recommendation: Pre-generate certificates when a custom domain is configured, not on first request.

26. File Watcher Performance (cli/lib/proxy-server.ts:342)

The file watcher watches the entire appdata-v1.json file. If this file becomes large with many sites, the watch events could fire frequently and cause cache invalidations.

Recommendation: Consider a more granular change detection or rate-limit cache invalidations.


🔒 Security Deep Dive

27. No Input Sanitization for Domain Names

Domain names from user input flow directly into:

  • Certificate generation (file paths)
  • Host header matching
  • File system operations

Risk: Path traversal attacks if someone puts ../../etc/passwd.local as a domain.

Recommendation: Strict domain validation using a whitelist pattern, not just domainToASCII().

28. Certificate Private Keys Not Protected

cli/lib/certificate-manager.ts:142-143:

fs.chmodSync(CA_CERT_PATH, 0o700);
fs.chmodSync(CA_KEY_PATH, 0o700);

Setting permissions to 700 on certificate files (read/write/execute for owner) is overly permissive. Keys should be 600 (read/write only).

Also, site certificate keys are written without setting permissions at all (line 298-299).

Recommendation:

fs.chmodSync(CA_KEY_PATH, 0o600);
// Also add for site keys:
fs.chmodSync(siteKeyPath, 0o600);

29. No Rate Limiting on Proxy Requests

The proxy has no rate limiting or abuse prevention. A malicious actor could flood the proxy with requests.

Recommendation: Implement rate limiting per host/IP.

30. Health Check Endpoint Exposes Information (cli/lib/proxy-server.ts:142-152)

The /__studio_health endpoint returns timestamps and server status. While useful for debugging, this could leak information to attackers.

Recommendation: Consider authentication or remove detailed information from the health check.


🎯 Architecture & Design Questions

31. Why PM2 Instead of Native Node.js Process Management?

As fredrikekelund mentioned in the review comments, this is a big architectural decision. PM2 adds significant complexity:

  • External dependency that's hard to bundle
  • Complex module resolution
  • Platform-specific behaviors
  • Need for daemon management

Questions:

  • Could this be implemented with Node.js child_process module directly?
  • What specific PM2 features are being used that justify the dependency?
  • Have alternatives been considered (systemd on Linux, launchd on macOS, Windows Services)?

Recommendation: Document the rationale for choosing PM2 in the PR description or a design doc.

32. Mixed Responsibilities in proxy-server.ts

This file handles:

  • HTTP/HTTPS server creation
  • Certificate generation (via import)
  • User data loading and parsing
  • File watching
  • Domain routing logic
  • Health checks

Recommendation: Consider splitting into smaller, focused modules:

  • proxy-routing.ts - Domain routing logic
  • proxy-config.ts - Configuration loading
  • proxy-health.ts - Health check endpoint

33. CLI-Specific node_modules Pattern

Having a separate package.json in cli/ is unusual and creates complexity:

  • Separate dependency tree
  • Needs to be copied during build
  • Complicates bundling strategy

Question: Is this pattern necessary? Could PM2 be in the root dependencies?


✅ Positive Observations

  1. Good use of path.join() - Most path operations correctly use path.join() for cross-platform compatibility
  2. Graceful shutdown handling - The proxy-server implements proper SIGTERM/SIGINT handlers
  3. Certificate caching - Certificates are cached on disk to avoid regeneration
  4. SNI support - Proper implementation of SNI for multi-domain HTTPS
  5. Type safety - Good use of TypeScript interfaces (though could be improved)
  6. Separation of concerns - CLI commands are separate from the core logic

📋 Action Items Summary

Before Merging (Blockers):

  1. Fix Windows compatibility (Issues Support WordPress in a subdirectory #1, Update/readme for launch #2, Release 1.0.0-beta.3 #3) - Critical blocker
  2. Add security documentation (Issue Studio: Clicking the user avatar in settings should got to their WP.com profile #5) - Explain root access requirements
  3. Add comprehensive tests (Issue Add site server process #19) - At minimum, unit tests for pm2-manager
  4. Fill out PR description (Issue Feature Request: one click to enable debug.log #23) - Explain what this PR does
  5. Investigate performance regression (Issue Feature Request: Catch all emails #24) - 22.6% slower site creation
  6. Fix file permissions on certificate keys (Issue Dragging doesn't work for the right (white) part of the initial app window #28) - Security issue

Nice to Have:

  1. Add JSDoc comments (Issue Run lint and unit tests on Buildkite #17)
  2. Implement proper logging instead of console.log (Issue Studio: Add translation to demo site expiration date #16)
  3. Add E2E tests (Issue Plugins and themes can't be symlinked #20)
  4. Update CLAUDE.md (Issue Feature Request: Support PhpStorm installed from JetBrains Toolbox app #21)
  5. Refactor for better separation of concerns (Issue Feature Request: Allow selecting the PHP version #32)

Discussion Needed:

  1. PM2 vs. homegrown solution (Issue Fix build CI job by updating macOS images #31) - As fredrikekelund mentioned
  2. Security implications of running as root (Issue Studio: Clicking the user avatar in settings should got to their WP.com profile #5)
  3. Whether to make HTTPS optional by default

🏁 Overall Assessment

This PR introduces powerful functionality for custom domain support, but it has significant cross-platform compatibility issues that must be addressed before merging. The Windows support is essentially non-functional, which is a blocker for a cross-platform application.

The code quality is generally good, but lacks tests and documentation. The 22.6% performance regression in site creation also needs investigation.

Recommendation: Request changes to address critical Windows compatibility issues and add test coverage before merging.


I've also posted inline comments on the most critical issues. Let me know if you need clarification on any of these points or would like me to help implement fixes for any of the issues!

@claude
Copy link

claude bot commented Nov 7, 2025

Claude Code is working…

I'll analyze this and get back to you.

View job run

min_uptime: '10s',
restart_delay: 3000,
kill_timeout: 5000,
uid: 0, // Run as root to bind to ports 80 and 443
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Critical Windows Compatibility Issue

The uid option doesn't work on Windows and will cause PM2 to fail. Windows doesn't use Unix-style UIDs.

Recommendation:

...(process.platform !== 'win32' && { uid: 0 }),

On Windows, you'll need a different approach to run with elevated privileges (the process itself needs to be started as Administrator).


// On Windows, we'd need to check for admin rights
// For now, we'll return false and handle Windows separately
// TODO: Implement Windows admin check
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Windows Admin Check Not Implemented

This TODO means the proxy feature will never work on Windows. You can implement Windows admin detection like this:

if (process.platform === 'win32') {
  try {
    const { execSync } = require('child_process');
    execSync('net session', { stdio: 'ignore' });
    return true;
  } catch {
    return false;
  }
}

Or use a library like is-elevated or check for specific Windows admin tokens.

export function isDaemonRunning(): boolean {
const homeDir = os.homedir();
const pm2Dir = path.join( homeDir, '.pm2' );
const rpcSocket = path.join( pm2Dir, 'rpc.sock' );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 Windows Named Pipes Not Handled

On Windows, PM2 uses named pipes instead of Unix sockets. The .sock file won't exist on Windows, so this check will always fail.

Recommendation:

const rpcSocket = process.platform === 'win32'
  ? path.join(pm2Dir, 'rpc.sock')  // On Windows PM2 uses different mechanism
  : path.join(pm2Dir, 'rpc.sock');

Or better yet, try connecting to PM2 and catch the error rather than checking for socket files.

/**
* Gets the site details for a given domain
*/
function getSiteByHost( domain: string ): SiteDetails | null {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Missing Domain Validation

Domain names from the Host header should be validated before use to prevent potential security issues:

function getSiteByHost( domain: string ): SiteDetails | null {
	// Validate domain format
	if (!domain || !/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/.test(domain)) {
		return null;
	}
	
	try {
		const sites = loadSites();
		// ... rest of function

This prevents malicious domains like ../../etc/passwd.local from being processed.

try {
return require( 'pm2' );
} catch ( error ) {
const possiblePaths: string[] = [
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Complex Module Resolution

This function tries 6 different paths to find PM2, which suggests the bundling strategy might need refinement. Consider:

  1. Ensure PM2 is properly included in the build via vite.cli.config.ts
  2. Or document why this complex resolution is necessary
  3. Add logging to help debug when PM2 isn't found

As mentioned in fredrikekelund's comment, moving CLI-specific dependencies to cli/package.json may not be ideal due to bundle size implications.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants