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>
This commit is contained in:
158
app/RSpade/BuildUI/resource/build-service/src/app.js
Executable file
158
app/RSpade/BuildUI/resource/build-service/src/app.js
Executable file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* 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;
|
||||
});
|
||||
Reference in New Issue
Block a user