const fs = require('fs'); const path = require('path'); const { execFileSync } = require('child_process'); const STATIC_INSTALL_DIRS = [ process.env.SEGGER_JLINK_PATH, process.env.JLINK_PATH, 'C:\\Program Files\\SEGGER\\JLink', 'C:\\Program Files\\SEGGER\\JLink_V942', 'C:\\Program Files (x86)\\SEGGER\\JLink', 'C:\\Program Files (x86)\\SEGGER\\JLink_V942', path.join(process.env.ProgramFiles || '', 'SEGGER', 'JLink'), path.join(process.env.ProgramFiles || '', 'SEGGER', 'JLink_V942'), path.join(process.env['ProgramFiles(x86)'] || '', 'SEGGER', 'JLink'), path.join(process.env['ProgramFiles(x86)'] || '', 'SEGGER', 'JLink_V942'), 'C:\\nrfutil\\bin', path.join(process.env.LOCALAPPDATA || '', 'Programs', 'SEGGER', 'JLink'), ].filter(Boolean); const SEGGER_ROOTS = [ path.join(process.env.ProgramFiles || 'C:\\Program Files', 'SEGGER'), path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'SEGGER'), path.join(process.env.LOCALAPPDATA || '', 'Programs', 'SEGGER'), ]; /** SEGGER installer default: Program Files\SEGGER\JLink_Vxxx */ const discoverSeggerJLinkDirs = () => { const discovered = []; for (const root of SEGGER_ROOTS) { if (!root || !fs.existsSync(root)) continue; let entries = []; try { entries = fs.readdirSync(root, { withFileTypes: true }); } catch { continue; } for (const entry of entries) { if (!entry.isDirectory()) continue; if (!/^JLink/i.test(entry.name)) continue; discovered.push(path.join(root, entry.name)); } } return discovered; }; const parseJLinkFolderVersion = directory => { const name = path.basename(directory); const match = name.match(/^JLink(?:_V)?(.+)$/i); if (!match) { return { sortKey: 0, label: name }; } const token = match[1]; const numeric = Number.parseInt(token, 10) || 0; const suffix = token.slice(String(numeric).length); return { sortKey: numeric * 1000 + (suffix ? suffix.charCodeAt(0) : 0), label: name, }; }; /** Prefer newest SEGGER pack (e.g. JLink_V942 over JLink_V898). */ const sortInstallDirsNewestFirst = directories => [...directories].sort((left, right) => { const leftVersion = parseJLinkFolderVersion(left); const rightVersion = parseJLinkFolderVersion(right); if (rightVersion.sortKey !== leftVersion.sortKey) { return rightVersion.sortKey - leftVersion.sortKey; } try { return fs.statSync(right).mtimeMs - fs.statSync(left).mtimeMs; } catch { return 0; } }); const getInstallDirectories = () => { const envDirs = [process.env.SEGGER_JLINK_PATH, process.env.JLINK_PATH].filter(Boolean); const discovered = sortInstallDirsNewestFirst(discoverSeggerJLinkDirs()); const staticDirs = STATIC_INSTALL_DIRS.filter( directory => directory && !envDirs.includes(directory) ); return [...new Set([...envDirs, ...discovered, ...staticDirs])]; }; const findExecutable = (name, directories = getInstallDirectories()) => { for (const directory of directories) { const candidate = path.join(directory, name); if (fs.existsSync(candidate)) { return candidate; } } return null; }; const findOnPath = name => { const pathEntries = (process.env.PATH || '').split(path.delimiter).filter(Boolean); for (const entry of pathEntries) { const candidate = path.join(entry, name); if (fs.existsSync(candidate)) { return candidate; } } return null; }; const getNrfjprogExe = () => findExecutable('nrfjprog.exe') || findOnPath('nrfjprog.exe'); const getJLinkExe = () => { const direct = findExecutable('JLink.exe') || findOnPath('JLink.exe'); if (direct) return direct; const nrfjprogExe = getNrfjprogExe(); if (nrfjprogExe) { const sibling = path.join(path.dirname(nrfjprogExe), 'JLink.exe'); if (fs.existsSync(sibling)) return sibling; } return null; }; const getJLinkGdbServerExe = () => { const candidates = ['JLinkGDBServerCL.exe', 'JLinkGDBServer.exe']; for (const name of candidates) { const direct = findExecutable(name) || findOnPath(name); if (direct) return direct; } const nrfjprogExe = getNrfjprogExe(); if (nrfjprogExe) { const directory = path.dirname(nrfjprogExe); for (const name of candidates) { const sibling = path.join(directory, name); if (fs.existsSync(sibling)) return sibling; } } return null; }; const hasSeggerRttStack = () => Boolean(getJLinkGdbServerExe()); let cachedDiagnostics; const clearJLinkDiagnosticsCache = () => { cachedDiagnostics = null; }; const getJLinkDiagnostics = () => { if (cachedDiagnostics) return cachedDiagnostics; let nrfjprogIds = []; const nrfjprogExe = getNrfjprogExe(); const jlinkExe = getJLinkExe(); const gdbServerExe = getJLinkGdbServerExe(); if (nrfjprogExe) { try { const output = execFileSync(nrfjprogExe, ['--ids'], { encoding: 'utf8', timeout: 10000, windowsHide: true, }); nrfjprogIds = output .split(/\r?\n/) .map(line => line.trim()) .filter(line => /^\d+$/.test(line)); } catch { nrfjprogIds = []; } } cachedDiagnostics = { nrfjprogExe, jlinkExe, gdbServerExe, gdbServerDir: gdbServerExe ? path.dirname(gdbServerExe) : '', nrfjprogIds, hasRttStack: Boolean(gdbServerExe), installDirectories: getInstallDirectories(), }; return cachedDiagnostics; }; module.exports = { STATIC_INSTALL_DIRS, getInstallDirectories, discoverSeggerJLinkDirs, findExecutable, findOnPath, getNrfjprogExe, getJLinkExe, getJLinkGdbServerExe, hasSeggerRttStack, getJLinkDiagnostics, clearJLinkDiagnosticsCache, };