@ai-capabilities-suite/mcp-debugger-core
Version:
Core debugging engine for Node.js and TypeScript applications. Provides Inspector Protocol integration, breakpoint management, variable inspection, execution control, profiling, hang detection, and source map support.
319 lines • 11.9 kB
JavaScript
;
/**
* Workspace-aware debugging support for monorepos and multi-package projects
* Handles workspace detection, package.json resolution, and workspace-relative paths
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.WorkspaceManager = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
/**
* Manages workspace-aware debugging for monorepos
*/
class WorkspaceManager {
constructor() {
this.workspaceCache = new Map();
}
/**
* Detect workspace structure from a given directory
*/
async detectWorkspace(cwd) {
// Check cache first
const cached = this.workspaceCache.get(cwd);
if (cached) {
return cached;
}
// Find workspace root
const root = await this.findWorkspaceRoot(cwd);
if (!root) {
return null;
}
// Detect workspace type and packages
const workspaceInfo = await this.analyzeWorkspace(root);
// Cache the result
if (workspaceInfo) {
this.workspaceCache.set(cwd, workspaceInfo);
this.workspaceCache.set(root, workspaceInfo);
}
return workspaceInfo;
}
/**
* Find the workspace root by looking for workspace configuration files
*/
async findWorkspaceRoot(startDir) {
let currentDir = path.resolve(startDir);
const rootDir = path.parse(currentDir).root;
while (currentDir !== rootDir) {
// Check for workspace indicators
const packageJsonPath = path.join(currentDir, "package.json");
if (fs.existsSync(packageJsonPath)) {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
// Check for npm/yarn workspaces
if (packageJson.workspaces) {
return currentDir;
}
// Check for pnpm workspace
if (fs.existsSync(path.join(currentDir, "pnpm-workspace.yaml"))) {
return currentDir;
}
// Check for lerna
if (fs.existsSync(path.join(currentDir, "lerna.json"))) {
return currentDir;
}
// Check for nx
if (fs.existsSync(path.join(currentDir, "nx.json"))) {
return currentDir;
}
}
// Move up one directory
const parentDir = path.dirname(currentDir);
if (parentDir === currentDir) {
break;
}
currentDir = parentDir;
}
return null;
}
/**
* Analyze workspace to determine type and find all packages
*/
async analyzeWorkspace(root) {
const packageJsonPath = path.join(root, "package.json");
if (!fs.existsSync(packageJsonPath)) {
return null;
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
// Detect workspace type
let type = "single";
let patterns = [];
if (fs.existsSync(path.join(root, "nx.json"))) {
type = "nx";
patterns = await this.getNxPackagePatterns(root);
}
else if (fs.existsSync(path.join(root, "lerna.json"))) {
type = "lerna";
const lernaJson = JSON.parse(fs.readFileSync(path.join(root, "lerna.json"), "utf-8"));
patterns = lernaJson.packages || ["packages/*"];
}
else if (fs.existsSync(path.join(root, "pnpm-workspace.yaml"))) {
type = "pnpm-workspaces";
patterns = await this.getPnpmWorkspacePatterns(root);
}
else if (packageJson.workspaces) {
// npm or yarn workspaces
if (Array.isArray(packageJson.workspaces)) {
patterns = packageJson.workspaces;
}
else if (packageJson.workspaces.packages) {
patterns = packageJson.workspaces.packages;
}
// Detect if it's npm or yarn
if (fs.existsSync(path.join(root, "yarn.lock"))) {
type = "yarn-workspaces";
}
else {
type = "npm-workspaces";
}
}
// Find all packages
const packages = await this.findPackages(root, patterns);
return {
root,
type,
packages,
};
}
/**
* Get package patterns from nx.json
*/
async getNxPackagePatterns(root) {
const nxJsonPath = path.join(root, "nx.json");
if (!fs.existsSync(nxJsonPath)) {
return [];
}
const nxJson = JSON.parse(fs.readFileSync(nxJsonPath, "utf-8"));
// Nx uses workspaceLayout or defaults
const appsDir = nxJson.workspaceLayout?.appsDir || "apps";
const libsDir = nxJson.workspaceLayout?.libsDir || "libs";
return [`${appsDir}/*`, `${libsDir}/*`, "packages/*"];
}
/**
* Get package patterns from pnpm-workspace.yaml
*/
async getPnpmWorkspacePatterns(root) {
const workspaceYamlPath = path.join(root, "pnpm-workspace.yaml");
if (!fs.existsSync(workspaceYamlPath)) {
return [];
}
// Simple YAML parsing for packages array
const content = fs.readFileSync(workspaceYamlPath, "utf-8");
const packagesMatch = content.match(/packages:\s*\n((?:\s+-\s+.+\n?)+)/);
if (!packagesMatch) {
return [];
}
const patterns = packagesMatch[1]
.split("\n")
.map((line) => line.trim())
.filter((line) => line.startsWith("-"))
.map((line) => line.substring(1).trim().replace(/['"]/g, ""));
return patterns;
}
/**
* Find all packages matching the given patterns
*/
async findPackages(root, patterns) {
const packages = [];
for (const pattern of patterns) {
const matches = await this.expandGlob(root, pattern);
for (const match of matches) {
const packageJsonPath = path.join(match, "package.json");
if (fs.existsSync(packageJsonPath)) {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
packages.push({
name: packageJson.name || path.basename(match),
path: match,
packageJson,
});
}
}
}
return packages;
}
/**
* Simple glob expansion (supports * wildcard)
*/
async expandGlob(root, pattern) {
const parts = pattern.split("/");
let currentPaths = [root];
for (const part of parts) {
const nextPaths = [];
for (const currentPath of currentPaths) {
if (part === "*") {
// Expand wildcard
if (fs.existsSync(currentPath)) {
const entries = fs.readdirSync(currentPath, {
withFileTypes: true,
});
for (const entry of entries) {
if (entry.isDirectory() && !entry.name.startsWith(".")) {
nextPaths.push(path.join(currentPath, entry.name));
}
}
}
}
else if (part === "**") {
// Recursive wildcard - not implemented for simplicity
nextPaths.push(currentPath);
}
else {
// Literal path component
nextPaths.push(path.join(currentPath, part));
}
}
currentPaths = nextPaths;
}
return currentPaths.filter((p) => fs.existsSync(p));
}
/**
* Find the package that contains a given file
*/
findPackageForFile(workspaceInfo, filePath) {
const absolutePath = path.resolve(filePath);
// Find the package whose path is a prefix of the file path
for (const pkg of workspaceInfo.packages) {
const pkgPath = path.resolve(pkg.path);
if (absolutePath.startsWith(pkgPath + path.sep) ||
absolutePath === pkgPath) {
return pkg;
}
}
return null;
}
/**
* Resolve a workspace-relative path to an absolute path
*/
resolveWorkspacePath(workspaceInfo, relativePath) {
// If it starts with a package name, resolve to that package
const match = relativePath.match(/^(@[^/]+\/[^/]+|[^/]+)\/(.*)/);
if (match) {
const [, packageName, packageRelativePath] = match;
const pkg = workspaceInfo.packages.find((p) => p.name === packageName);
if (pkg) {
return path.join(pkg.path, packageRelativePath);
}
}
// Otherwise, resolve relative to workspace root
return path.join(workspaceInfo.root, relativePath);
}
/**
* Convert an absolute path to a workspace-relative path
*/
makeWorkspaceRelative(workspaceInfo, absolutePath) {
const resolved = path.resolve(absolutePath);
const pkg = this.findPackageForFile(workspaceInfo, resolved);
if (pkg) {
const relativeToPkg = path.relative(pkg.path, resolved);
return `${pkg.name}/${relativeToPkg}`;
}
// Fall back to root-relative path
return path.relative(workspaceInfo.root, resolved);
}
/**
* Get all package.json files in the workspace
*/
getAllPackageJsons(workspaceInfo) {
const packageJsons = [path.join(workspaceInfo.root, "package.json")];
for (const pkg of workspaceInfo.packages) {
packageJsons.push(path.join(pkg.path, "package.json"));
}
return packageJsons;
}
/**
* Find the appropriate working directory for debugging a file
*/
getWorkingDirectoryForFile(workspaceInfo, filePath) {
const pkg = this.findPackageForFile(workspaceInfo, filePath);
return pkg ? pkg.path : workspaceInfo.root;
}
/**
* Clear the workspace cache
*/
clearCache() {
this.workspaceCache.clear();
}
}
exports.WorkspaceManager = WorkspaceManager;
//# sourceMappingURL=workspace-manager.js.map