Skip to main content
Glama
base-scaffolder.js5.03 kB
#!/usr/bin/env node /** * Base scaffolder class for domain generation */ import { spawn } from "node:child_process"; import fs from "node:fs/promises"; import path from "node:path"; import { DOMAINS_DIR, fileUtils, summaryUtils, TEMPLATES_DIR, templateUtils, validationUtils, } from "./scaffolding-utils.js"; export class BaseScaffolder { constructor() { this.config = { name: "", description: "", apiEndpoint: "", tools: [], resources: [], cliCommands: [], }; } /** * Validate the configuration */ async validateConfig() { validationUtils.validateDomainName(this.config.name); if (await validationUtils.domainExists(this.config.name)) { throw new Error(`Domain "${this.config.name}" already exists`); } if (!this.config.description) { throw new Error("Domain description is required"); } } /** * Generate a single file from template */ async generateFile(templateName, outputName) { const templatePath = path.join(TEMPLATES_DIR, `${templateName}.template`); const outputPath = path.join(DOMAINS_DIR, this.config.name, outputName); // Read template const templateContent = await fs.readFile(templatePath, "utf-8"); // Get replacements and conditions const replacements = fileUtils.getTemplateReplacements(this.config); const conditions = fileUtils.getTemplateConditions(this.config); // Process template let processed = templateUtils.processTemplate( templateContent, replacements, ); processed = templateUtils.processConditionalSections(processed, conditions); // Write file await fs.writeFile(outputPath, processed); return outputName; } /** * Generate all domain files */ async generateDomain() { // Validate configuration await this.validateConfig(); // Create domain directory const domainPath = path.join(DOMAINS_DIR, this.config.name); await fs.mkdir(domainPath, { recursive: true }); // Files to generate const filesToGenerate = [ { template: "domain.types.ts", output: `${this.config.name}.types.ts` }, { template: "domain.service.ts", output: `${this.config.name}.service.ts`, }, { template: "domain.formatter.ts", output: `${this.config.name}.formatter.ts`, }, { template: "domain.controller.ts", output: `${this.config.name}.controller.ts`, }, { template: "domain.tool.ts", output: `${this.config.name}.tool.ts` }, { template: "domain.resource.ts", output: `${this.config.name}.resource.ts`, }, { template: "domain.cli.ts", output: `${this.config.name}.cli.ts` }, { template: "domain.index.ts", output: "index.ts" }, ]; // Generate files const generatedFiles = []; for (const { template, output } of filesToGenerate) { try { await this.generateFile(template, output); generatedFiles.push(output); } catch (error) { console.error(`Failed to generate ${output}:`, error.message); throw error; } } return generatedFiles; } /** * Display summary after generation */ displaySummary(generatedFiles) { console.log(summaryUtils.generateSummary(this.config, generatedFiles)); } /** * Run biome formatter on generated files */ async formatGeneratedFiles() { const domainPath = path.join(DOMAINS_DIR, this.config.name); return new Promise((resolve, _reject) => { console.log("\n📐 Running formatter on generated files..."); const biome = spawn("npx", ["biome", "format", domainPath, "--write"], { cwd: path.dirname(DOMAINS_DIR), // Run from src directory stdio: "pipe", }); let _output = ""; let errorOutput = ""; biome.stdout.on("data", (data) => { _output += data.toString(); }); biome.stderr.on("data", (data) => { errorOutput += data.toString(); }); biome.on("close", (code) => { if (code === 0) { console.log("✅ Files formatted successfully"); resolve(); } else { // Biome might exit with non-zero even if formatting succeeded // Check if there are actual errors vs just warnings if (errorOutput.includes("error") || errorOutput.includes("Error")) { console.warn("⚠️ Formatting completed with warnings"); console.log(errorOutput); } // Resolve anyway as formatting issues shouldn't block generation resolve(); } }); biome.on("error", (err) => { console.warn("⚠️ Could not run formatter:", err.message); // Don't reject - formatting is nice to have but not critical resolve(); }); }); } /** * Main execution flow */ async execute() { try { const files = await this.generateDomain(); await this.formatGeneratedFiles(); this.displaySummary(files); } catch (error) { console.error("Error:", error.message); process.exit(1); } } /** * Set configuration (to be overridden by subclasses) */ async configure() { throw new Error("configure() must be implemented by subclass"); } /** * Run the scaffolder */ async run() { await this.configure(); await this.execute(); } }

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