@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
145 lines (144 loc) • 5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CliTestUtil = void 0;
const child_process_1 = require("child_process");
const path_1 = require("path");
/**
* Utility for testing CLI commands with real execution
*/
class CliTestUtil {
/**
* Executes a CLI command and returns the result
*
* @param args - CLI arguments to pass to the command
* @param options - Test options
* @returns Promise with stdout, stderr, and exit code
*/
static async runCommand(args, options = {}) {
// Default timeout of 30 seconds
const timeoutMs = options.timeoutMs || 30000;
// CLI execution path - points to the built CLI script
const cliPath = (0, path_1.join)(process.cwd(), 'dist', 'index.js');
return new Promise((resolve, reject) => {
// Set up timeout handler
const timeoutId = setTimeout(() => {
child.kill();
reject(new Error(`CLI command timed out after ${timeoutMs}ms`));
}, timeoutMs);
// Capture stdout and stderr
let stdout = '';
let stderr = '';
// Spawn the process with given arguments
const child = (0, child_process_1.spawn)('node', [cliPath, ...args], {
env: {
...process.env,
...options.env,
},
});
// Collect stdout data
child.stdout.on('data', (data) => {
stdout += data.toString();
});
// Collect stderr data
child.stderr.on('data', (data) => {
stderr += data.toString();
});
// Handle process completion
child.on('close', (exitCode) => {
clearTimeout(timeoutId);
resolve({
stdout,
stderr,
exitCode: exitCode ?? 0,
});
});
// Handle process errors
child.on('error', (err) => {
clearTimeout(timeoutId);
reject(err);
});
});
}
/**
* Validates that stdout contains expected strings/patterns
*/
static validateOutputContains(output, expectedPatterns) {
for (const pattern of expectedPatterns) {
if (typeof pattern === 'string') {
expect(output).toContain(pattern);
}
else {
expect(output).toMatch(pattern);
}
}
}
/**
* Validates Markdown output format
*/
static validateMarkdownOutput(output) {
// Check for Markdown heading
expect(output).toMatch(/^#\s.+/m);
// Check for markdown formatting elements like bold text, lists, etc.
const markdownElements = [
/\*\*.+\*\*/, // Bold text
/-\s.+/, // List items
/\|.+\|.+\|/, // Table rows
/\[.+\]\(.+\)/, // Links
];
expect(markdownElements.some((pattern) => pattern.test(output))).toBe(true);
}
/**
* Extracts and parses JSON from CLI output
* Handles output that may contain log lines before the JSON
*
* @param output - The CLI output string
* @returns Parsed JSON object or null if no valid JSON found
*/
static extractJsonFromOutput(output) {
// Split by newlines and find lines that could be start of JSON
const lines = output.split('\n');
let jsonStartIndex = -1;
// Find the first line that starts with '{' (the actual JSON output)
for (let i = 0; i < lines.length; i++) {
const trimmed = lines[i].trim();
if (trimmed.startsWith('{') && !trimmed.includes('[')) {
// This looks like start of JSON, not a log line with timestamp
jsonStartIndex = i;
break;
}
}
if (jsonStartIndex === -1) {
return null;
}
// Join from the JSON start to the end
const jsonStr = lines.slice(jsonStartIndex).join('\n');
try {
return JSON.parse(jsonStr);
}
catch {
// Try to find the matching closing brace
let braceCount = 0;
let endIndex = 0;
for (let i = 0; i < jsonStr.length; i++) {
if (jsonStr[i] === '{')
braceCount++;
if (jsonStr[i] === '}')
braceCount--;
if (braceCount === 0) {
endIndex = i + 1;
break;
}
}
if (endIndex > 0) {
try {
return JSON.parse(jsonStr.substring(0, endIndex));
}
catch {
return null;
}
}
return null;
}
}
}
exports.CliTestUtil = CliTestUtil;