const fs = require('fs'); const os = require('os'); const path = require('path'); const { execFile } = require('child_process'); const { promisify } = require('util'); const { getJLinkExe, getNrfjprogExe, getJLinkDiagnostics, clearJLinkDiagnosticsCache, } = require('./jlinkPaths.cjs'); const execFileAsync = promisify(execFile); const parseShowEmuList = output => { const probes = []; const patterns = [ /Serial number:\s*(\d+).*?(?:ProductName|Product name):\s*([^,\r\n]+).*?(?:Connection):\s*([^,\r\n]+)/i, /Serial number:\s*(\d+).*?(?:ProductName|Product name):\s*([^,\r\n]+)/i, /Serial number:\s*(\d+)/i, ]; for (const line of output.split(/\r?\n/)) { if (!/serial number/i.test(line)) continue; let serialNumber; let productName = 'J-Link'; let connection = 'USB'; for (const pattern of patterns) { const match = line.match(pattern); if (!match) continue; serialNumber = match[1]; if (match[2]) productName = match[2].trim(); if (match[3]) connection = match[3].trim(); break; } if (!serialNumber) continue; probes.push({ path: serialNumber, serialNumber, friendlyName: productName, manufacturer: 'SEGGER', connection, source: 'jlink', }); } return probes; }; const parseNrfjprogIds = output => output .split(/\r?\n/) .map(line => line.trim()) .filter(line => /^\d+$/.test(line)) .map(serialNumber => ({ path: serialNumber, serialNumber, friendlyName: `J-Link (${serialNumber})`, manufacturer: 'SEGGER', connection: 'USB', source: 'nrfjprog', })); const listViaJLinkExe = async jlinkExe => { const scriptPath = path.join(os.tmpdir(), `dtm-jlink-emu-${process.pid}.jlink`); await fs.promises.writeFile(scriptPath, 'ShowEmuList\nexit\n', 'utf8'); try { const { stdout, stderr } = await execFileAsync( jlinkExe, ['-CommandFile', scriptPath, '-nogui', '1'], { timeout: 15000, windowsHide: true, maxBuffer: 1024 * 1024, } ); return parseShowEmuList(`${stdout}\n${stderr}`); } finally { await fs.promises.unlink(scriptPath).catch(() => {}); } }; const listViaNrfjprog = async nrfjprogExe => { const { stdout, stderr } = await execFileAsync(nrfjprogExe, ['--ids'], { timeout: 10000, windowsHide: true, maxBuffer: 1024 * 1024, }); return parseNrfjprogIds(`${stdout}\n${stderr}`); }; const listJLinkProbes = async () => { clearJLinkDiagnosticsCache(); const jlinkExe = getJLinkExe(); const nrfjprogExe = getNrfjprogExe(); const errors = []; if (jlinkExe) { try { const probes = await listViaJLinkExe(jlinkExe); if (probes.length > 0) { return probes; } } catch (error) { errors.push( error instanceof Error ? error.message : String(error) ); } } if (nrfjprogExe) { try { const probes = await listViaNrfjprog(nrfjprogExe); if (probes.length > 0) { return probes; } } catch (error) { errors.push( error instanceof Error ? error.message : String(error) ); } } const diagnostics = getJLinkDiagnostics(); if (!jlinkExe && !nrfjprogExe) { throw new Error( 'nrfjprog and JLink.exe were not found. Install nRF Command Line Tools or SEGGER J-Link software, or set SEGGER_JLINK_PATH.' ); } if (diagnostics.nrfjprogIds.length > 0) { return parseNrfjprogIds(diagnostics.nrfjprogIds.join('\n')); } const detail = errors.length > 0 ? ` (${errors.join('; ')})` : ''; throw new Error( `No J-Link responded on USB. Plug in the probe, close Ozone or RTT Viewer, then refresh.${detail}` ); }; const getJLinkSetupHint = () => { const diagnostics = getJLinkDiagnostics(); if (!diagnostics.gdbServerExe) { const ids = diagnostics.nrfjprogIds.length > 0 ? ` Detected probe SN: ${diagnostics.nrfjprogIds.join(', ')}.` : ''; const searched = diagnostics.installDirectories?.length > 0 ? ` Searched: ${diagnostics.installDirectories.slice(0, 4).join('; ')}…` : ''; return ( 'RTT needs JLinkGDBServerCL.exe (e.g. C:\\Program Files\\SEGGER\\JLink_V942). Install or set SEGGER_JLINK_PATH.' + ids + searched + ' nrfjprog alone is not enough for RTT.' ); } return ''; }; module.exports = { listJLinkProbes, parseShowEmuList, parseNrfjprogIds, getJLinkSetupHint, };