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