- Updated regex patterns in chipUtils and mentionLinkify to enhance boundary detection for mentions. - Refactored taskChangeRequest to simplify earliest date calculation using array destructuring. - Improved taskReferenceUtils by replacing character boundary checks with a more concise regex. - Enhanced teamMessageFiltering to ensure boolean checks for message filtering conditions. - Adjusted urlMatchUtils to refine URL matching regex for better accuracy. - Updated crossTeam constants to include comments for regex patterns, improving code clarity. - Removed unused CommentAttachmentPayload type from api.ts to clean up type definitions. - Introduced McpInstallScope type for better type safety in mcp.ts. - Enhanced extensionNormalizers to improve URL normalization and added tests for parseGitHubOwnerRepo function. - Cleaned up pricing.ts by removing unnecessary eslint disable comments. - Added tests for new functionality in chipUtils and crossTeam constants, ensuring robust coverage.
130 lines
3.7 KiB
TypeScript
130 lines
3.7 KiB
TypeScript
/**
|
|
* Pure-function normalizers for Extension Store data.
|
|
*/
|
|
|
|
import type { PluginCapability, PluginCatalogItem } from '@shared/types/extensions';
|
|
|
|
/**
|
|
* Normalize a repository URL for dedup comparison.
|
|
* Lowercases, strips `.git` suffix, strips trailing `/`.
|
|
*/
|
|
export function normalizeRepoUrl(url: string): string {
|
|
return url
|
|
.toLowerCase()
|
|
.replace(/\.git$/, '')
|
|
.replace(
|
|
/* eslint-disable-next-line sonarjs/slow-regex -- trailing slashes only, URL length bounded */
|
|
/\/+$/,
|
|
''
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Derive UI-visible capability labels from plugin capability flags.
|
|
*/
|
|
export function inferCapabilities(item: PluginCatalogItem): PluginCapability[] {
|
|
const caps: PluginCapability[] = [];
|
|
if (item.hasLspServers) caps.push('lsp');
|
|
if (item.hasMcpServers) caps.push('mcp');
|
|
if (item.hasAgents) caps.push('agent');
|
|
if (item.hasCommands) caps.push('command');
|
|
if (item.hasHooks) caps.push('hook');
|
|
if (caps.length === 0) caps.push('skill');
|
|
return caps;
|
|
}
|
|
|
|
const CAPABILITY_LABELS: Record<PluginCapability, string> = {
|
|
lsp: 'LSP',
|
|
mcp: 'MCP',
|
|
agent: 'Agent',
|
|
command: 'Command',
|
|
hook: 'Hook',
|
|
skill: 'Skill',
|
|
};
|
|
|
|
/**
|
|
* Get a human-readable label for the primary capability.
|
|
*/
|
|
export function getPrimaryCapabilityLabel(capabilities: PluginCapability[]): string {
|
|
if (capabilities.length === 0) return 'Skill';
|
|
return CAPABILITY_LABELS[capabilities[0]];
|
|
}
|
|
|
|
/**
|
|
* Get human-readable label for a capability.
|
|
*/
|
|
export function getCapabilityLabel(capability: PluginCapability): string {
|
|
return CAPABILITY_LABELS[capability];
|
|
}
|
|
|
|
/**
|
|
* Format large install counts for display.
|
|
* 277472 → "277K", 1200000 → "1.2M", 42 → "42"
|
|
*/
|
|
export function formatInstallCount(count: number): string {
|
|
if (count >= 1_000_000) {
|
|
const millions = count / 1_000_000;
|
|
return millions >= 10
|
|
? `${Math.round(millions)}M`
|
|
: `${millions.toFixed(1).replace(/\.0$/, '')}M`;
|
|
}
|
|
if (count >= 1_000) {
|
|
const thousands = count / 1_000;
|
|
return thousands >= 10
|
|
? `${Math.round(thousands)}K`
|
|
: `${thousands.toFixed(1).replace(/\.0$/, '')}K`;
|
|
}
|
|
return String(count);
|
|
}
|
|
|
|
/**
|
|
* Normalize a category string for consistent comparison/display.
|
|
* Lowercases, trims, falls back to "other".
|
|
*/
|
|
export function normalizeCategory(raw: string | undefined): string {
|
|
if (!raw) return 'other';
|
|
const normalized = raw.trim().toLowerCase();
|
|
return normalized || 'other';
|
|
}
|
|
|
|
/**
|
|
* Build a pluginId (= qualifiedName) from marketplace plugin name + marketplace name.
|
|
*/
|
|
export function buildPluginId(pluginName: string, marketplaceName: string): string {
|
|
return `${pluginName}@${marketplaceName}`;
|
|
}
|
|
|
|
/**
|
|
* Sanitize an MCP server display name into a CLI-safe server name.
|
|
* Must match the regex /^[\w.-]{1,100}$/ required by McpInstallService.
|
|
*/
|
|
export function sanitizeMcpServerName(displayName: string): string {
|
|
return displayName
|
|
.toLowerCase()
|
|
.replace(/\s+/g, '-')
|
|
.replace(/[^\w.-]/g, '');
|
|
}
|
|
|
|
/**
|
|
* Extract owner/repo from a GitHub URL. Returns null for non-GitHub URLs.
|
|
* Handles: https://github.com/owner/repo, https://github.com/owner/repo.git, trailing slashes.
|
|
*/
|
|
export function parseGitHubOwnerRepo(url: string): { owner: string; repo: string } | null {
|
|
try {
|
|
const parsed = new URL(url);
|
|
if (parsed.hostname !== 'github.com') return null;
|
|
const parts = parsed.pathname
|
|
.replace(/^\//, '')
|
|
.replace(/\.git$/, '')
|
|
.replace(
|
|
/* eslint-disable-next-line sonarjs/slow-regex -- trailing slashes only, pathname bounded */
|
|
/\/+$/,
|
|
''
|
|
)
|
|
.split('/');
|
|
if (parts.length < 2 || !parts[0] || !parts[1]) return null;
|
|
return { owner: parts[0], repo: parts[1] };
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|