feat(platform): improve cross-platform compatibility with Windows support

Enhance application to work seamlessly across operating systems by implementing platform-specific adaptations:

- Update node-pty to version 1.1.0-beta34 for Windows support
- Implement PowerShell terminal support for Windows environments
- Integrate cross-spawn library for reliable process execution on all platforms
- Standardize development ports to industry defaults (3001/5173)
- Add dynamic shell detection based on operating system

BREAKING CHANGE: Development server ports have been changed from 3008/3009 to 3001/5173 to align with standard Vite/Express defaults
This commit is contained in:
OhMyApps
2025-07-23 18:36:29 +02:00
parent 6db8be5f54
commit 2ff59bd28c
6 changed files with 1817 additions and 1289 deletions

View File

@@ -7,6 +7,6 @@
# Backend server port (Express API + WebSocket server) # Backend server port (Express API + WebSocket server)
#API server #API server
PORT=3008 PORT=3001
#Frontend port #Frontend port
VITE_PORT=3009 VITE_PORT=5173

1462
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -39,13 +39,14 @@
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"cross-spawn": "^7.0.3",
"express": "^4.18.2", "express": "^4.18.2",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"lucide-react": "^0.515.0", "lucide-react": "^0.515.0",
"mime-types": "^3.0.1", "mime-types": "^3.0.1",
"multer": "^2.0.1", "multer": "^2.0.1",
"node-fetch": "^2.7.0", "node-fetch": "^2.7.0",
"node-pty": "^1.0.0", "node-pty": "^1.1.0-beta34",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",

View File

@@ -1,8 +1,12 @@
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import crossSpawn from 'cross-spawn';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import path from 'path'; import path from 'path';
import os from 'os'; import os from 'os';
// Use cross-spawn on Windows for better command execution
const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn;
let activeClaudeProcesses = new Map(); // Track active processes by session ID let activeClaudeProcesses = new Map(); // Track active processes by session ID
async function spawnClaude(command, options = {}, ws) { async function spawnClaude(command, options = {}, ws) {
@@ -23,7 +27,10 @@ async function spawnClaude(command, options = {}, ws) {
// Add print flag with command if we have a command // Add print flag with command if we have a command
if (command && command.trim()) { if (command && command.trim()) {
args.push('--print', command); // Separate arguments for better cross-platform compatibility
// This prevents issues with spaces and quotes on Windows
args.push('--print');
args.push(command);
} }
// Use cwd (actual project directory) instead of projectPath (Claude's metadata directory) // Use cwd (actual project directory) instead of projectPath (Claude's metadata directory)
@@ -63,9 +70,9 @@ async function spawnClaude(command, options = {}, ws) {
const imageNote = `\n\n[Images provided at the following paths:]\n${tempImagePaths.map((p, i) => `${i + 1}. ${p}`).join('\n')}`; const imageNote = `\n\n[Images provided at the following paths:]\n${tempImagePaths.map((p, i) => `${i + 1}. ${p}`).join('\n')}`;
const modifiedCommand = command + imageNote; const modifiedCommand = command + imageNote;
// Update the command in args // Update the command in args - now that --print and command are separate
const printIndex = args.indexOf('--print'); const printIndex = args.indexOf('--print');
if (printIndex !== -1 && args[printIndex + 1] === command) { if (printIndex !== -1 && printIndex + 1 < args.length && args[printIndex + 1] === command) {
args[printIndex + 1] = modifiedCommand; args[printIndex + 1] = modifiedCommand;
} }
} }
@@ -227,7 +234,7 @@ async function spawnClaude(command, options = {}, ws) {
console.log('🔍 Full command args:', JSON.stringify(args, null, 2)); console.log('🔍 Full command args:', JSON.stringify(args, null, 2));
console.log('🔍 Final Claude command will be: claude ' + args.join(' ')); console.log('🔍 Final Claude command will be: claude ' + args.join(' '));
const claudeProcess = spawn('claude', args, { const claudeProcess = spawnFunction('claude', args, {
cwd: workingDir, cwd: workingDir,
stdio: ['pipe', 'pipe', 'pipe'], stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env } // Inherit all environment variables env: { ...process.env } // Inherit all environment variables

View File

@@ -515,32 +515,41 @@ function handleShellConnection(ws) {
})); }));
try { try {
// Build shell command that changes to project directory first, then runs claude // Prepare the shell command adapted to the platform
let claudeCommand = 'claude'; let shellCommand;
if (os.platform() === 'win32') {
if (hasSession && sessionId) { if (hasSession && sessionId) {
// Try to resume session, but with fallback to new session if it fails // Try to resume session, but with fallback to new session if it fails
claudeCommand = `claude --resume ${sessionId} || claude`; shellCommand = `Set-Location -Path "${projectPath}"; claude --resume ${sessionId}; if ($LASTEXITCODE -ne 0) { claude }`;
} else {
shellCommand = `Set-Location -Path "${projectPath}"; claude`;
}
} else {
if (hasSession && sessionId) {
shellCommand = `cd "${projectPath}" && claude --resume ${sessionId} || claude`;
} else {
shellCommand = `cd "${projectPath}" && claude`;
}
} }
// Create shell command that cds to the project directory first
const shellCommand = `cd "${projectPath}" && ${claudeCommand}`;
console.log('🔧 Executing shell command:', shellCommand); console.log('🔧 Executing shell command:', shellCommand);
// Start shell using PTY for proper terminal emulation // Use appropriate shell based on platform
shellProcess = pty.spawn('bash', ['-c', shellCommand], { const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
const shellArgs = os.platform() === 'win32' ? ['-Command', shellCommand] : ['-c', shellCommand];
shellProcess = pty.spawn(shell, shellArgs, {
name: 'xterm-256color', name: 'xterm-256color',
cols: 80, cols: 80,
rows: 24, rows: 24,
cwd: process.env.HOME || '/', // Start from home directory cwd: process.env.HOME || (os.platform() === 'win32' ? process.env.USERPROFILE : '/'),
env: { env: {
...process.env, ...process.env,
TERM: 'xterm-256color', TERM: 'xterm-256color',
COLORTERM: 'truecolor', COLORTERM: 'truecolor',
FORCE_COLOR: '3', FORCE_COLOR: '3',
// Override browser opening commands to echo URL for detection // Override browser opening commands to echo URL for detection
BROWSER: 'echo "OPEN_URL:"' BROWSER: os.platform() === 'win32' ? 'echo "OPEN_URL:"' : 'echo "OPEN_URL:"'
} }
}); });
@@ -889,12 +898,7 @@ app.post('/api/projects/:projectName/upload-images', authenticateToken, async (r
// Serve React app for all other routes // Serve React app for all other routes
app.get('*', (req, res) => { app.get('*', (req, res) => {
if (process.env.NODE_ENV === 'production') {
res.sendFile(path.join(__dirname, '../dist/index.html')); res.sendFile(path.join(__dirname, '../dist/index.html'));
} else {
// In development, redirect to Vite dev server
res.redirect(`http://localhost:${process.env.VITE_PORT || 3001}`);
}
}); });
// Helper function to convert permissions to rwx format // Helper function to convert permissions to rwx format

View File

@@ -9,11 +9,11 @@ export default defineConfig(({ command, mode }) => {
return { return {
plugins: [react()], plugins: [react()],
server: { server: {
port: parseInt(env.VITE_PORT) || 3001, port: parseInt(env.VITE_PORT) || 5173,
proxy: { proxy: {
'/api': `http://localhost:${env.PORT || 3002}`, '/api': `http://localhost:${env.PORT || 3001}`,
'/ws': { '/ws': {
target: `ws://localhost:${env.PORT || 3002}`, target: `ws://localhost:${env.PORT || 3001}`,
ws: true ws: true
} }
} }