ucm-mcp-server
Version:
Universal Context Manager MCP Server - AI-native artifact management
178 lines • 6.46 kB
JavaScript
/**
* Utility functions for parsing UCM paths into component parts
*/
/**
* Parse a UCM path into its component parts
* Supports formats like:
* - "utaba/main/commands/user" (namespace only)
* - "utaba/main/commands/user/CreateUserCommand.ts" (with filename)
* - "utaba/main/commands/user/CreateUserCommand.ts@1.0.0" (with version)
*/
export function parsePath(path) {
if (!path)
path = "";
// Remove leading/trailing slashes
const cleanPath = path.replace(/^\/+|\/+$/g, '');
// Split into parts
const parts = cleanPath.split('/');
const result = {
author: parts.length > 0 ? parts[0] : undefined,
repository: parts.length > 1 ? parts[1] : undefined,
category: parts.length > 2 ? parts[2] : undefined,
subcategory: parts.length > 3 ? parts[3] : undefined,
};
if (parts.length > 4) {
let filenameParts = parts[4].split('@');
//filename
result.filename = filenameParts[0];
if (filenameParts.length > 1) {
result.version = filenameParts[1];
}
}
return result;
}
/**
* Build a namespace path from components (author/repository/category/subcategory)
*/
export function buildNamespacePath(author, repository, category, subcategory) {
return `${author}/${repository}/${category}/${subcategory}`;
}
/**
* Build a full path with filename and optional version
*/
export function buildFullPath(author, repository, category, subcategory, filename, version) {
let path = buildNamespacePath(author, repository, category, subcategory);
if (filename) {
path += `/${filename}`;
if (version) {
path += `@${version}`;
}
}
return path;
}
/**
* Extract namespace from a full path
*/
export function extractNamespace(path) {
const parsed = parsePath(path);
return buildNamespacePath(parsed.author || '', parsed.repository || '', parsed.category || '', parsed.subcategory || '');
}
/**
* Validate if a path follows UCM naming conventions
*/
export function validatePath(path) {
const errors = [];
try {
const parsed = parsePath(path);
// Validate author (lowercase alphanumeric and hyphens)
if (!/^[a-z0-9-]+$/.test(parsed.author || '')) {
errors.push('Author must contain only lowercase letters, numbers, and hyphens');
}
// Validate repository (lowercase alphanumeric and hyphens)
if (!/^[a-z0-9-]+$/.test(parsed.repository || '')) {
errors.push('Repository must contain only lowercase letters, numbers, and hyphens');
}
// Validate category (must be valid UCM category)
const validCategories = ['commands', 'services', 'patterns', 'implementations', 'contracts', 'guidance', 'project'];
if (!validCategories.includes(parsed.category || '')) {
errors.push(`Category must be one of: ${validCategories.join(', ')}`);
}
// Validate subcategory (lowercase alphanumeric and hyphens)
if (!/^[a-z0-9-]+$/.test(parsed.subcategory || '')) {
errors.push('Subcategory must contain only lowercase letters, numbers, and hyphens');
}
// Validate filename if present
if (parsed.filename) {
if (!/^[a-zA-Z0-9._-]+\.[a-z]+$/.test(parsed.filename)) {
errors.push('Filename must be valid with an extension');
}
}
// Validate version if present
if (parsed.version) {
if (!/^v?\d+\.\d+\.\d+(-[a-z0-9-]+)?(\+[a-z0-9-]+)?$/.test(parsed.version)) {
errors.push('Version must follow semantic versioning (e.g., 1.0.0, v2.1.0)');
}
}
}
catch (error) {
errors.push(error instanceof Error ? error.message : 'Invalid path format');
}
return {
isValid: errors.length === 0,
errors
};
}
/**
* Compare two semantic versions
* Returns: -1 if version1 < version2, 0 if equal, 1 if version1 > version2
*/
export function compareVersions(version1, version2) {
// Normalize versions by removing 'v' prefix if present
const v1 = version1.replace(/^v/, '');
const v2 = version2.replace(/^v/, '');
// Split versions into parts
const parts1 = v1.split(/[.-]/);
const parts2 = v2.split(/[.-]/);
// Compare major, minor, patch
for (let i = 0; i < 3; i++) {
const num1 = parseInt(parts1[i] || '0', 10);
const num2 = parseInt(parts2[i] || '0', 10);
if (num1 > num2)
return 1;
if (num1 < num2)
return -1;
}
// If major.minor.patch are equal, compare pre-release versions
const preRelease1 = parts1.slice(3).join('.');
const preRelease2 = parts2.slice(3).join('.');
// No pre-release is greater than pre-release
if (!preRelease1 && preRelease2)
return 1;
if (preRelease1 && !preRelease2)
return -1;
if (!preRelease1 && !preRelease2)
return 0;
// Compare pre-release versions lexically
return preRelease1.localeCompare(preRelease2);
}
/**
* Check if a version is greater than another version
*/
export function isVersionGreater(version1, version2) {
return compareVersions(version1, version2) > 0;
}
/**
* Check if a version is less than another version
*/
export function isVersionLess(version1, version2) {
return compareVersions(version1, version2) < 0;
}
/**
* Check if two versions are equal
*/
export function isVersionEqual(version1, version2) {
return compareVersions(version1, version2) === 0;
}
/**
* Increment the patch version of a semantic version
* Examples: 1.0.0 -> 1.0.1, 2.1.5 -> 2.1.6
*/
export function incrementPatchVersion(version) {
// Normalize version by removing 'v' prefix if present
const normalized = version.replace(/^v/, '');
// Split version into parts
const parts = normalized.split('.');
if (parts.length < 3) {
throw new Error(`Invalid semantic version format: ${version}`);
}
// Parse major, minor, patch
const major = parseInt(parts[0], 10);
const minor = parseInt(parts[1], 10);
const patch = parseInt(parts[2].split('-')[0], 10); // Handle pre-release versions
if (isNaN(major) || isNaN(minor) || isNaN(patch)) {
throw new Error(`Invalid semantic version format: ${version}`);
}
// Increment patch version
return `${major}.${minor}.${patch + 1}`;
}
//# sourceMappingURL=PathUtils.js.map