Files
rspade_system/app/RSpade/BuildUI/resource/build-service/rspade-build-ui-server.js
root d7d341f752 Update npm packages to latest versions
Fix JavaScript sourcemap paths to show full file locations
Implement --build-debug flag and complete Build UI streaming
Add xterm.js terminal UI and fix asset path routing
Add RSpade Build UI service with WebSocket support

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-31 08:12:33 +00:00

186 lines
4.9 KiB
JavaScript
Executable File

const express = require('express');
const { WebSocketServer } = require('ws');
const http = require('http');
const path = require('path');
const { spawn } = require('child_process');
const app = express();
const PORT = 3100;
const WS_PORT = 3101;
// Build process state
let buildProcess = null;
let buildBuffer = [];
let isBuildRunning = false;
// Serve static files from public directory
app.use(express.static(path.join(__dirname, 'public')));
// Serve node_modules for xterm.css and other assets
app.use('/node_modules', express.static(path.join(__dirname, 'node_modules')));
// Hello world demo page
app.get('/_hello_world', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'hello_world.html'));
});
// Hello world endpoint
app.get('/api/hello', (req, res) => {
res.json({ message: 'Hello from RSpade Build UI Service!' });
});
// Health check endpoint
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Start HTTP server
const server = http.createServer(app);
server.listen(PORT, () => {
console.log(`[BuildUI] HTTP server running on http://localhost:${PORT}`);
});
// Create WebSocket server
const wss = new WebSocketServer({ port: WS_PORT });
// Track connected clients
let clients = new Set();
// Broadcast message to all connected clients
function broadcast(message) {
const json = JSON.stringify(message);
clients.forEach(client => {
if (client.readyState === 1) { // OPEN
client.send(json);
}
});
}
// Start build process
function startBuild() {
if (isBuildRunning) {
console.log('[BuildUI] Build already running, skipping');
return;
}
console.log('[BuildUI] Starting build process...');
isBuildRunning = true;
buildBuffer = [];
// Spawn php artisan command with --build-debug flag
buildProcess = spawn('php', ['artisan', 'rsx:bundle:compile', '--build-debug'], {
cwd: '/var/www/html',
env: process.env
});
// Capture stdout
buildProcess.stdout.on('data', (data) => {
const content = data.toString();
buildBuffer.push(content);
broadcast({
type: 'output',
content: content
});
});
// Capture stderr
buildProcess.stderr.on('data', (data) => {
const content = data.toString();
buildBuffer.push(content);
broadcast({
type: 'output',
content: content
});
});
// Handle completion
buildProcess.on('close', (code) => {
console.log(`[BuildUI] Build process exited with code ${code}`);
isBuildRunning = false;
buildProcess = null;
broadcast({
type: 'build_complete',
exit_code: code,
timestamp: new Date().toISOString()
});
});
// Handle errors
buildProcess.on('error', (error) => {
console.error('[BuildUI] Build process error:', error);
isBuildRunning = false;
buildProcess = null;
broadcast({
type: 'build_error',
error: error.message,
timestamp: new Date().toISOString()
});
});
}
wss.on('connection', (ws) => {
console.log('[BuildUI] Client connected to WebSocket');
clients.add(ws);
// Send welcome message
ws.send(JSON.stringify({
type: 'connected',
message: 'Connected to RSpade Build UI WebSocket',
timestamp: new Date().toISOString()
}));
// If build is running, send buffered output to new client
if (isBuildRunning && buildBuffer.length > 0) {
console.log(`[BuildUI] Sending ${buildBuffer.length} buffered chunks to new client`);
buildBuffer.forEach(content => {
ws.send(JSON.stringify({
type: 'output',
content: content
}));
});
}
// Start build if not already running
if (!isBuildRunning) {
startBuild();
}
// Handle incoming messages
ws.on('message', (data) => {
try {
const message = JSON.parse(data.toString());
console.log('[BuildUI] Received message:', message);
} catch (error) {
console.error('[BuildUI] Error parsing message:', error);
}
});
// Handle disconnect
ws.on('close', () => {
console.log('[BuildUI] Client disconnected from WebSocket');
clients.delete(ws);
});
// Handle errors
ws.on('error', (error) => {
console.error('[BuildUI] WebSocket error:', error);
clients.delete(ws);
});
});
console.log(`[BuildUI] WebSocket server running on ws://localhost:${WS_PORT}`);
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('[BuildUI] SIGTERM received, shutting down gracefully');
server.close(() => {
console.log('[BuildUI] HTTP server closed');
wss.close(() => {
console.log('[BuildUI] WebSocket server closed');
process.exit(0);
});
});
});