feat(debugger): integrate NX Tcl Remote Debugger into NX Postprocessor
build_and_puplish.yml / build_and_publish (release) Successful in 35s
build_and_puplish.yml / build_and_publish (release) Successful in 35s
Embed the NX Tcl Remote Debugger into the NX Postprocessor extension. Add a new debugging client, protocol, and adapter logic to drive attach/launch, breakpoints, stepping, and evaluation via the runtime adapter. - Introduced a client debugger with a new adapter and protocol - Wired attach/launch, breakpoints, stepping, and evaluation - Updated docs and licensing to reflect the embedded debugger
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
'use strict';
|
||||
|
||||
const net = require('net');
|
||||
const { EventEmitter } = require('events');
|
||||
|
||||
function encodeHex(value) {
|
||||
return Buffer.from(String(value ?? ''), 'utf8').toString('hex') || '-';
|
||||
}
|
||||
|
||||
function decodeHex(value) {
|
||||
if (!value || value === '-') return '';
|
||||
return Buffer.from(value, 'hex').toString('utf8');
|
||||
}
|
||||
|
||||
class RuntimeConnection extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.socket = null;
|
||||
this.buffer = '';
|
||||
this.nextRequestId = 1;
|
||||
this.pending = new Map();
|
||||
this.closed = false;
|
||||
}
|
||||
|
||||
connect(host, port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = net.createConnection({ host, port });
|
||||
const onError = (error) => {
|
||||
socket.destroy();
|
||||
reject(error);
|
||||
};
|
||||
socket.once('error', onError);
|
||||
socket.once('connect', () => {
|
||||
socket.removeListener('error', onError);
|
||||
this.socket = socket;
|
||||
this.closed = false;
|
||||
socket.setEncoding('utf8');
|
||||
socket.on('data', (chunk) => this.onData(chunk));
|
||||
socket.on('error', (error) => this.emit('runtimeError', error));
|
||||
socket.on('close', () => this.onClose());
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
request(command, fields = [], timeoutMs = 30000) {
|
||||
if (!this.socket || this.closed) {
|
||||
return Promise.reject(new Error('NX runtime is not connected'));
|
||||
}
|
||||
const id = this.nextRequestId++;
|
||||
const normalized = fields.map((field) => String(field ?? ''));
|
||||
if (normalized.some((field) => field.includes('\t') || field.includes('\n'))) {
|
||||
return Promise.reject(new Error('protocol fields must be hex-encoded before transmission'));
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
this.pending.delete(id);
|
||||
reject(new Error(`NX ${command} request timed out`));
|
||||
}, timeoutMs);
|
||||
this.pending.set(id, { resolve, reject, timer, command });
|
||||
this.socket.write(['REQ', id, command, ...normalized].join('\t') + '\n');
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.closed = true;
|
||||
if (this.socket) this.socket.destroy();
|
||||
this.socket = null;
|
||||
}
|
||||
|
||||
onData(chunk) {
|
||||
this.buffer += chunk;
|
||||
let newline;
|
||||
while ((newline = this.buffer.indexOf('\n')) >= 0) {
|
||||
const line = this.buffer.slice(0, newline).replace(/\r$/, '');
|
||||
this.buffer = this.buffer.slice(newline + 1);
|
||||
if (line) this.onLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
onLine(line) {
|
||||
const fields = line.split('\t');
|
||||
if (fields[0] === 'RES') {
|
||||
const id = Number(fields[1]);
|
||||
const pending = this.pending.get(id);
|
||||
if (!pending) return;
|
||||
this.pending.delete(id);
|
||||
clearTimeout(pending.timer);
|
||||
if (fields[2] === 'OK') {
|
||||
pending.resolve({ command: fields[3], fields: fields.slice(4) });
|
||||
} else {
|
||||
pending.reject(new Error(decodeHex(fields[4]) || `NX ${pending.command} failed`));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (fields[0] === 'EVENT') {
|
||||
this.emit('event', fields[1], fields.slice(2));
|
||||
return;
|
||||
}
|
||||
this.emit('runtimeError', new Error(`unrecognized NX message: ${line}`));
|
||||
}
|
||||
|
||||
onClose() {
|
||||
if (this.closed) return;
|
||||
this.closed = true;
|
||||
for (const pending of this.pending.values()) {
|
||||
clearTimeout(pending.timer);
|
||||
pending.reject(new Error('NX runtime disconnected'));
|
||||
}
|
||||
this.pending.clear();
|
||||
this.emit('close');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { RuntimeConnection, encodeHex, decodeHex };
|
||||
Reference in New Issue
Block a user