Files
rspade_system/app/RSpade/BuildUI/resource/build-service/src/app.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

159 lines
6.0 KiB
JavaScript
Executable File

/**
* RSpade Build UI - Terminal Application
*/
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
// Use nginx-proxied WebSocket path
const WS_URL = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/_build/ws`;
class BuildTerminal {
constructor() {
this.ws = null;
this.connected = false;
this.term = null;
this.fitAddon = null;
}
initialize() {
// Create terminal instance
this.term = new Terminal({
theme: {
background: '#1e1e1e',
foreground: '#d4d4d4',
cursor: '#4ec9b0',
cursorAccent: '#1e1e1e',
black: '#000000',
red: '#cd3131',
green: '#0dbc79',
yellow: '#e5e510',
blue: '#2472c8',
magenta: '#bc3fbc',
cyan: '#11a8cd',
white: '#e5e5e5',
brightBlack: '#666666',
brightRed: '#f14c4c',
brightGreen: '#23d18b',
brightYellow: '#f5f543',
brightBlue: '#3b8eea',
brightMagenta: '#d670d6',
brightCyan: '#29b8db',
brightWhite: '#e5e5e5'
},
fontSize: 14,
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
cursorBlink: true,
cursorStyle: 'block',
scrollback: 10000,
convertEol: true
});
// Create fit addon for responsive sizing
this.fitAddon = new FitAddon();
this.term.loadAddon(this.fitAddon);
// Open terminal in the container
const container = document.getElementById('terminal');
this.term.open(container);
// Fit to container size
this.fitAddon.fit();
// Handle window resize
window.addEventListener('resize', () => {
this.fitAddon.fit();
});
// Write welcome message
this.term.writeln('\x1b[1;32m╔════════════════════════════════════════════════════════════════╗\x1b[0m');
this.term.writeln('\x1b[1;32m║\x1b[0m \x1b[1;36mRSpade Asset Compiler\x1b[0m \x1b[1;32m║\x1b[0m');
this.term.writeln('\x1b[1;32m╚════════════════════════════════════════════════════════════════╝\x1b[0m');
this.term.writeln('');
// Connect to WebSocket
this.connectWebSocket();
}
connectWebSocket() {
this.term.writeln('\x1b[36m[INFO]\x1b[0m Connecting to WebSocket...');
try {
this.ws = new WebSocket(WS_URL);
this.ws.onopen = () => {
console.log('[BuildUI] WebSocket connected');
this.connected = true;
this.term.writeln('\x1b[32m[OK]\x1b[0m WebSocket connected');
this.term.writeln('');
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('[BuildUI] Received:', data);
// Display message in terminal
if (data.type === 'connected') {
this.term.writeln(`\x1b[36m[SERVER]\x1b[0m ${data.message}`);
this.term.writeln('');
} else if (data.type === 'output') {
this.term.write(data.content);
} else if (data.type === 'build_complete') {
this.term.writeln('');
this.term.writeln('\x1b[90m' + '─'.repeat(64) + '\x1b[0m');
if (data.exit_code === 0) {
this.term.writeln('\x1b[1;32m[BUILD COMPLETE]\x1b[0m Build finished successfully!');
} else {
this.term.writeln(`\x1b[1;31m[BUILD FAILED]\x1b[0m Build exited with code ${data.exit_code}`);
}
this.term.writeln('\x1b[90m' + '─'.repeat(64) + '\x1b[0m');
this.term.writeln('');
} else if (data.type === 'build_error') {
this.term.writeln('');
this.term.writeln(`\x1b[31m[ERROR]\x1b[0m Build error: ${data.error}`);
this.term.writeln('');
} else {
this.term.writeln(`\x1b[90m[${data.type}]\x1b[0m ${JSON.stringify(data)}`);
}
};
this.ws.onerror = (error) => {
console.error('[BuildUI] WebSocket error:', error);
this.term.writeln('\x1b[31m[ERROR]\x1b[0m WebSocket connection error');
};
this.ws.onclose = () => {
console.log('[BuildUI] WebSocket disconnected');
this.connected = false;
this.term.writeln('\x1b[33m[WARN]\x1b[0m WebSocket disconnected');
this.term.writeln('\x1b[36m[INFO]\x1b[0m Reconnecting in 2 seconds...');
// Attempt reconnect after 2 seconds
setTimeout(() => this.connectWebSocket(), 2000);
};
} catch (error) {
console.error('[BuildUI] Connection error:', error);
this.term.writeln('\x1b[31m[ERROR]\x1b[0m Connection failed');
}
}
sendMessage(type, payload) {
if (this.connected) {
this.ws.send(JSON.stringify({ type, ...payload }));
} else {
console.error('[BuildUI] Cannot send message - not connected');
}
}
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
console.log('[BuildUI] Initializing terminal...');
const terminal = new BuildTerminal();
terminal.initialize();
// Make terminal available globally for testing
window.buildTerminal = terminal;
});