UNPKG

@aashari/mcp-server-atlassian-bitbucket

Version:

Node.js/TypeScript MCP server for Atlassian Bitbucket. Enables AI systems (LLMs) to interact with workspaces, repositories, and pull requests via tools (list, get, comment, search). Connects AI directly to version control workflows through the standard MC

344 lines (343 loc) 9.78 kB
"use strict"; /** * Utility functions for converting Atlassian Document Format (ADF) to Markdown */ Object.defineProperty(exports, "__esModule", { value: true }); exports.adfToMarkdown = adfToMarkdown; const logger_util_js_1 = require("./logger.util.js"); // Create a file-level logger for the module const adfLogger = logger_util_js_1.Logger.forContext('utils/adf.util.ts'); /** * Convert Atlassian Document Format (ADF) to Markdown * * @param adf - The ADF content to convert (can be string or object) * @returns The converted Markdown content */ function adfToMarkdown(adf) { try { // Handle empty or undefined input if (!adf) { return ''; } // Parse ADF if it's a string let adfDoc; if (typeof adf === 'string') { try { adfDoc = JSON.parse(adf); } catch { return adf; // Return as-is if not valid JSON } } else if (typeof adf === 'object') { adfDoc = adf; } else { return String(adf); } // Check if it's a valid ADF document if (!adfDoc.content || !Array.isArray(adfDoc.content)) { return ''; } // Process the document return processAdfContent(adfDoc.content); } catch (error) { adfLogger.error('Error converting ADF to Markdown:', error); return '*Error converting description format*'; } } /** * Process ADF content nodes */ function processAdfContent(content) { if (!content || !Array.isArray(content)) { return ''; } return content.map((node) => processAdfNode(node)).join('\n\n'); } /** * Process mention node */ function processMention(node) { if (!node.attrs) { return ''; } const text = node.attrs.text || node.attrs.displayName || ''; if (!text) { return ''; } // Format as @username to preserve the mention format // Remove any existing @ symbol to avoid double @@ in the output const cleanText = typeof text === 'string' && text.startsWith('@') ? text.substring(1) : text; return `@${cleanText}`; } /** * Process a single ADF node */ function processAdfNode(node) { if (!node || !node.type) { return ''; } switch (node.type) { case 'paragraph': return processParagraph(node); case 'heading': return processHeading(node); case 'bulletList': return processBulletList(node); case 'orderedList': return processOrderedList(node); case 'listItem': return processListItem(node); case 'codeBlock': return processCodeBlock(node); case 'blockquote': return processBlockquote(node); case 'rule': return '---'; case 'mediaGroup': return processMediaGroup(node); case 'table': return processTable(node); case 'text': return processText(node); case 'mention': return processMention(node); default: // For unknown node types, try to process content if available if (node.content) { return processAdfContent(node.content); } return ''; } } /** * Process paragraph node */ function processParagraph(node) { if (!node.content) { return ''; } // Process each child node and join them with proper spacing return node.content .map((childNode, index) => { // Add a space between text nodes if needed const needsSpace = index > 0 && childNode.type === 'text' && node.content[index - 1].type === 'text' && !childNode.text?.startsWith(' ') && !node.content[index - 1].text?.endsWith(' '); return (needsSpace ? ' ' : '') + processAdfNode(childNode); }) .join(''); } /** * Process heading node */ function processHeading(node) { if (!node.content || !node.attrs) { return ''; } const level = typeof node.attrs.level === 'number' ? node.attrs.level : 1; const headingMarker = '#'.repeat(level); const content = node.content .map((childNode) => processAdfNode(childNode)) .join(''); return `${headingMarker} ${content}`; } /** * Process bullet list node */ function processBulletList(node) { if (!node.content) { return ''; } return node.content.map((item) => processAdfNode(item)).join('\n'); } /** * Process ordered list node */ function processOrderedList(node) { if (!node.content) { return ''; } return node.content .map((item, index) => { const processedItem = processAdfNode(item); // Replace the first "- " with "1. ", "2. ", etc. return processedItem.replace(/^- /, `${index + 1}. `); }) .join('\n'); } /** * Process list item node */ function processListItem(node) { if (!node.content) { return ''; } const content = node.content .map((childNode) => { const processed = processAdfNode(childNode); // For nested lists, add indentation if (childNode.type === 'bulletList' || childNode.type === 'orderedList') { return processed .split('\n') .map((line) => ` ${line}`) .join('\n'); } return processed; }) .join('\n'); return `- ${content}`; } /** * Process code block node */ function processCodeBlock(node) { if (!node.content) { return '```\n```'; } const language = node.attrs?.language || ''; const code = node.content .map((childNode) => processAdfNode(childNode)) .join(''); return `\`\`\`${language}\n${code}\n\`\`\``; } /** * Process blockquote node */ function processBlockquote(node) { if (!node.content) { return ''; } const content = node.content .map((childNode) => processAdfNode(childNode)) .join('\n\n'); // Add > to each line return content .split('\n') .map((line) => `> ${line}`) .join('\n'); } /** * Process media group node */ function processMediaGroup(node) { if (!node.content) { return ''; } return node.content .map((mediaNode) => { if (mediaNode.type === 'media' && mediaNode.attrs) { const { id, type } = mediaNode.attrs; if (type === 'file') { return `[Attachment: ${id}]`; } else if (type === 'link') { return `[External Link]`; } } return ''; }) .filter(Boolean) .join('\n'); } /** * Process table node */ function processTable(node) { if (!node.content) { return ''; } const rows = []; // Process table rows node.content.forEach((row) => { if (row.type === 'tableRow' && row.content) { const cells = []; row.content.forEach((cell) => { if ((cell.type === 'tableCell' || cell.type === 'tableHeader') && cell.content) { const cellContent = cell.content .map((cellNode) => processAdfNode(cellNode)) .join(''); cells.push(cellContent.trim()); } }); if (cells.length > 0) { rows.push(cells); } } }); if (rows.length === 0) { return ''; } // Create markdown table const columnCount = Math.max(...rows.map((row) => row.length)); // Ensure all rows have the same number of columns const normalizedRows = rows.map((row) => { while (row.length < columnCount) { row.push(''); } return row; }); // Create header row const headerRow = normalizedRows[0].map((cell) => cell || ''); // Create separator row const separatorRow = headerRow.map(() => '---'); // Create content rows const contentRows = normalizedRows.slice(1); // Build the table const tableRows = [ headerRow.join(' | '), separatorRow.join(' | '), ...contentRows.map((row) => row.join(' | ')), ]; return tableRows.join('\n'); } /** * Process text node */ function processText(node) { if (!node.text) { return ''; } let text = node.text; // Apply marks if available if (node.marks && node.marks.length > 0) { // Process link marks last to avoid issues with other formatting const linkMark = node.marks.find((mark) => mark.type === 'link'); const otherMarks = node.marks.filter((mark) => mark.type !== 'link'); // Apply non-link marks first otherMarks.forEach((mark) => { switch (mark.type) { case 'strong': text = `**${text}**`; break; case 'em': text = `*${text}*`; break; case 'code': text = `\`${text}\``; break; case 'strike': text = `~~${text}~~`; break; case 'underline': // Markdown doesn't support underline, use emphasis instead text = `_${text}_`; break; } }); // Apply link mark last if (linkMark && linkMark.attrs && linkMark.attrs.href) { text = `[${text}](${linkMark.attrs.href})`; } } return text; }