Skip to main content
Glama
scaffolding-utils.js5.61 kB
#!/usr/bin/env node /** * Shared utilities for domain scaffolding scripts */ import path from "node:path"; import { fileURLToPath } from "node:url"; // Get __dirname equivalent in ES modules const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Path constants export const PROJECT_ROOT = path.resolve(__dirname, "../../"); export const DOMAINS_DIR = path.join(PROJECT_ROOT, "src/domains"); export const TEMPLATES_DIR = path.join(PROJECT_ROOT, "scripts/templates"); /** * String transformation utilities */ export const stringUtils = { toPascalCase(str) { return str .split(/[-_\s]+/) .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(""); }, toKebabCase(str) { return str .replace(/([a-z])([A-Z])/g, "$1-$2") .replace(/[\s_]+/g, "-") .toLowerCase(); }, toCamelCase(str) { const pascal = this.toPascalCase(str); return pascal.charAt(0).toLowerCase() + pascal.slice(1); }, }; /** * Template processing utilities */ export const templateUtils = { /** * Process template content with replacements */ processTemplate(content, replacements) { let processed = content; for (const [key, value] of Object.entries(replacements)) { const regex = new RegExp(`{{${key}}}`, "g"); processed = processed.replace(regex, value); } return processed; }, /** * Process conditional sections in templates */ processConditionalSections(content, conditions) { let processed = content; // First, find all conditional blocks in the template const ifBlockRegex = /{{#if:(\w+)}}([\s\S]*?){{\/if:\1}}/g; const unlessBlockRegex = /{{#unless:(\w+)}}([\s\S]*?){{\/unless:\1}}/g; // Process if blocks processed = processed.replace(ifBlockRegex, (_match, condition, block) => { // If condition exists and is true, keep the block content // Otherwise, remove the entire block return conditions[condition] ? block : ""; }); // Process unless blocks processed = processed.replace( unlessBlockRegex, (_match, condition, block) => { // If condition exists and is false (or doesn't exist), keep the block content // Otherwise, remove the entire block return !conditions[condition] ? block : ""; }, ); return processed; }, }; /** * File generation utilities */ export const fileUtils = { /** * Get all template replacements for a domain */ getTemplateReplacements(config) { const domainPascal = stringUtils.toPascalCase(config.name); const domainCamel = stringUtils.toCamelCase(config.name); const domainKebab = stringUtils.toKebabCase(config.name); return { domainName: config.name, domainNamePascal: domainPascal, domainNameCamel: domainCamel, domainNameKebab: domainKebab, domainDescription: config.description, apiEndpoint: config.apiEndpoint || `/${config.name}`, toolsCount: (config.tools || []).length, resourcesCount: (config.resources || []).length, cliCommandsCount: (config.cliCommands || []).length, date: new Date().toISOString().split("T")[0], ...(config.customReplacements || {}), }; }, /** * Get template conditions based on configuration */ getTemplateConditions(config) { const conditions = {}; // Add tool conditions (templates use tool names directly) if (config.tools) { config.tools.forEach((tool) => { conditions[tool] = true; }); } // Add resource conditions if (config.resources) { config.resources.forEach((resource) => { conditions[resource] = true; }); } // Add any custom conditions Object.assign(conditions, config.customConditions || {}); return conditions; }, }; /** * Validation utilities */ export const validationUtils = { /** * Validate domain name */ validateDomainName(name) { if (!name || typeof name !== "string") { throw new Error("Domain name is required"); } if (!/^[a-z][a-z0-9-]*$/.test(name)) { throw new Error( "Domain name must start with lowercase letter and contain only lowercase letters, numbers, and hyphens", ); } return true; }, /** * Check if domain already exists */ async domainExists(domainName) { const fs = await import("node:fs/promises"); const domainPath = path.join(DOMAINS_DIR, domainName); try { await fs.access(domainPath); return true; } catch { return false; } }, }; /** * Summary display utilities */ export const summaryUtils = { /** * Format file list for display */ formatFileList(files, domainName) { return files .map((file) => ` - src/domains/${domainName}/${file}`) .join("\n"); }, /** * Get summary statistics */ getStats(config) { const toolCount = config.tools?.length || 0; const resourceCount = config.resources?.length || 0; const cliCount = config.cliCommands?.length || 0; return { toolCount, resourceCount, cliCount, totalFiles: 8, // Standard domain structure }; }, /** * Generate summary message */ generateSummary(config, files) { const stats = this.getStats(config); return ` Domain "${config.name}" scaffolded successfully! Created files: ${this.formatFileList(files, config.name)} Summary: - ${stats.toolCount} MCP tools - ${stats.resourceCount} MCP resources - ${stats.cliCount} CLI commands - ${stats.totalFiles} total files Next steps: 1. Update the service layer with actual Lokalise API calls 2. Customize the formatters for your specific needs 3. Add proper error handling and validation 4. Write tests for your new domain Run the following to see your new CLI commands: npm run cli -- --help `; }, };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/AbdallahAHO/lokalise-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server