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>
186 lines
4.9 KiB
JavaScript
Executable File
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);
|
|
});
|
|
});
|
|
});
|