import { MCPTool } from "./index.js";
import { COMPONENTS_DB } from "../data/components.js";
export const generateComponentTool: MCPTool = {
name: "generate_ntv_component_file",
description:
"Generates a complete TypeScript component file that uses an NTV component",
inputSchema: {
type: "object",
properties: {
component: {
type: "string",
description: "Component name (e.g., 'Button', 'Input')",
},
filename: {
type: "string",
description:
"Output filename without extension (e.g., 'my-button'). Default: component name in kebab-case",
},
selector: {
type: "string",
description: "Angular component selector (e.g., 'app-my-button')",
},
includeStyles: {
type: "boolean",
description: "Include CSS file template. Default: true",
},
includeTests: {
type: "boolean",
description: "Include Jest/Jasmine test template. Default: true",
},
},
required: ["component"],
},
execute: async (args: Record<string, unknown>) => {
const componentName = args.component as string;
const filename = (args.filename as string) || toKebabCase(componentName);
const selector = (args.selector as string) || `app-${filename}`;
const includeStyles = args.includeStyles !== false;
const includeTests = args.includeTests !== false;
const component = COMPONENTS_DB.find(
(c) => c.name.toLowerCase() === componentName.toLowerCase()
);
if (!component) {
throw new Error(`Component '${componentName}' not found`);
}
const files: Record<string, string> = {};
// Generate TypeScript file
files[`${filename}.ts`] = generateTypeScriptFile(component, selector);
// Generate HTML template
files[`${filename}.html`] = generateTemplateFile(component, selector);
// Generate CSS file
if (includeStyles) {
files[`${filename}.css`] = generateCSSFile();
}
// Generate test file
if (includeTests) {
files[`${filename}.spec.ts`] = generateTestFile(component, selector, filename);
}
return {
component: componentName,
files,
componentClass: toPascalCase(filename),
selector,
note: "All files are ready to be created in your Angular project",
};
},
};
function generateTypeScriptFile(component: any, selector: string): string {
const className = toPascalCase(selector.replace("app-", ""));
return `import { Component, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ${component.name}${component.configInterface ? `, ${component.configInterface}` : ""} } from '@ntv-scaffolding/component-pantry';
@Component({
selector: '${selector}',
standalone: true,
imports: [CommonModule, ${component.name}],
templateUrl: './${selector.replace("app-", "")}.html',
styleUrls: ['./${selector.replace("app-", "")}.css'],
})
export class ${className} {
// Component logic here
onAction() {
console.log('Action triggered');
}
}`;
}
function generateTemplateFile(component: any, selector: string): string {
return `<!-- ${selector} component template -->
<div class="container">
<${component.selector}>
<!-- Add content here -->
</${component.selector}>
</div>`;
}
function generateCSSFile(): string {
return `/* Component styles */
:host {
display: block;
}
.container {
padding: 1rem;
}`;
}
function generateTestFile(component: any, selector: string, filename: string): string {
const className = toPascalCase(filename);
return `import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ${className} } from './${filename}';
describe('${className}', () => {
let component: ${className};
let fixture: ComponentFixture<${className}>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [${className}],
}).compileComponents();
fixture = TestBed.createComponent(${className});
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should handle action', () => {
spyOn(console, 'log');
component.onAction();
expect(console.log).toHaveBeenCalledWith('Action triggered');
});
});`;
}
function toKebabCase(str: string): string {
return str
.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2")
.toLowerCase();
}
function toPascalCase(str: string): string {
return str
.split("-")
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join("");
}