From 4cbf711068459d2ab060496201d932b64a871834 Mon Sep 17 00:00:00 2001 From: Kirill Date: Thu, 2 Oct 2025 15:56:00 +0300 Subject: [PATCH 1/2] fix: make params optional --- docs/component-refactoring-flow.md | 6 ++- docs/contracts.md | 12 +++-- docs/ds-refactoring-flow.md | 6 ++- docs/tools.md | 10 ++-- .../builder/build-component-contract.tool.ts | 15 ++++-- .../builder/models/schema.ts | 6 +-- .../builder/utils/build-contract.ts | 54 ++++++++++--------- 7 files changed, 63 insertions(+), 46 deletions(-) diff --git a/docs/component-refactoring-flow.md b/docs/component-refactoring-flow.md index 61a6a72..07ec1a5 100644 --- a/docs/component-refactoring-flow.md +++ b/docs/component-refactoring-flow.md @@ -155,9 +155,10 @@ At this point, all checklist items have been processed. You must review the refa ### Tools used - `build_component_contract` - Creates component contracts for safe refactoring - - Parameters: `componentFile`, `dsComponentName` (set to "AUTO") + - Parameters: `saveLocation`, `typescriptFile` (required), `templateFile` (optional), `styleFile` (optional), `dsComponentName` (optional, set to "AUTO") - Returns: contract path with component's public API, DOM structure, and styles - Purpose: Establish baseline for validation comparison + - Note: Template and style files are optional for components with inline templates/styles - `get-ds-component-data` - Retrieves Design System component information when needed - Parameters: `componentName`, `sections` (optional) - Array of sections to include: "implementation", "documentation", "stories", "all" @@ -234,9 +235,10 @@ The rule implements a comprehensive validation process: ### Tools used - `build_component_contract` - Creates post-refactor component contract - - Parameters: `saveLocation`, `templateFile`, `styleFile`, `typescriptFile`, `dsComponentName` + - Parameters: `saveLocation`, `typescriptFile` (required), `templateFile` (optional), `styleFile` (optional), `dsComponentName` (optional) - Returns: updated contract path with refactored component state - Purpose: Capture final component state for comparison + - Note: Template and style files are optional for components with inline templates/styles - `diff_component_contract` - Compares baseline and updated contracts - Parameters: `saveLocation`, `contractBeforePath`, `contractAfterPath`, `dsComponentName` diff --git a/docs/contracts.md b/docs/contracts.md index 29a8ef7..be04088 100644 --- a/docs/contracts.md +++ b/docs/contracts.md @@ -65,17 +65,19 @@ Because contracts track every public-facing facet of a component, any refactor t Rules taking care about the contract building during the workflow, but if you need to build it "manually" say in the chat: ``` -build_component_contract(saveLocation, templateFile, styleFile, typescriptFile, dsComponentName) +build_component_contract(saveLocation, typescriptFile, templateFile?, styleFile?, dsComponentName?) ``` > Replace the parameters with: > - `saveLocation`: Path where to save the contract file (supports absolute and relative paths) -> - `templateFile`: Path to the component template file (.html or .ts for inline) -> - `styleFile`: Path to the component style file (.scss, .css, etc.) -> - `typescriptFile`: Path to the TypeScript component file (.ts) -> - `dsComponentName`: Optional design system component name (e.g., `DsBadge`) +> - `typescriptFile`: Path to the TypeScript component file (.ts) — **Required** +> - `templateFile`: *(Optional)* Path to the component template file (.html). Omit for inline templates +> - `styleFile`: *(Optional)* Path to the component style file (.scss, .css, etc.). Omit for inline styles or components without styles +> - `dsComponentName`: *(Optional)* Design system component name (e.g., `DsBadge`) > > The tool analyses the template, TypeScript, and styles, then saves the contract to your specified location. +> +> **Note**: Angular components can have inline templates and styles. If `templateFile` or `styleFile` are not provided, the tool will extract inline template/styles from the TypeScript file. ## When to Build a Contract diff --git a/docs/ds-refactoring-flow.md b/docs/ds-refactoring-flow.md index b6427eb..fcaeedd 100644 --- a/docs/ds-refactoring-flow.md +++ b/docs/ds-refactoring-flow.md @@ -479,9 +479,10 @@ At this point, initial refactoring is complete. ### Tools used - `build_component_contract` - Creates component contracts for safe refactoring - - Parameters: `directory`, `templateFile`, `styleFile`, `typescriptFile`, `dsComponentName` + - Parameters: `saveLocation`, `typescriptFile` (required), `templateFile` (optional), `styleFile` (optional), `dsComponentName` (optional) - Returns: contract with public API, DOM structure, styles, and metadata - Generates: JSON contract files with SHA-256 hashes for validation + - Note: Template and style files are optional—extracts inline templates/styles from TypeScript when not provided ### Flow @@ -566,9 +567,10 @@ This is your last chance to make changes before opening the pull request. - Features: Automatic ESLint config resolution, comprehensive rule coverage - `build_component_contract` - Creates contracts for refactored components - - Parameters: `saveLocation`, `templateFile`, `styleFile`, `typescriptFile`, `dsComponentName` + - Parameters: `saveLocation`, `typescriptFile` (required), `templateFile` (optional), `styleFile` (optional), `dsComponentName` (optional) - Returns: JSON contract with public API, DOM structure, and styles - Purpose: Capture post-refactoring component state + - Note: Template and style files are optional for components with inline templates/styles - `list_component_contracts` - Lists available component contracts - Parameters: `directory` diff --git a/docs/tools.md b/docs/tools.md index 3756c91..fa5dcfb 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -114,12 +114,12 @@ This document provides comprehensive guidance for AI agents working with Angular **AI Usage**: Generate contracts before refactoring to track breaking changes **Key Parameters**: - `saveLocation`: Path where to save the contract file (supports absolute and relative paths) -- `templateFile`: Template file name (.html or .ts for inline) -- `styleFile`: Style file name (.scss, .css, etc.) -- `typescriptFile`: TypeScript component file (.ts) -- `dsComponentName`: Optional design system component name +- `typescriptFile`: **Required** TypeScript component file (.ts) +- `templateFile`: *Optional* Template file name (.html). Omit for inline templates +- `styleFile`: *Optional* Style file name (.scss, .css, etc.). Omit for inline styles or no styles +- `dsComponentName`: *Optional* design system component name **Output**: Component contract file with API surface -**Best Practice**: Create contracts before major refactoring for comparison +**Best Practice**: Create contracts before major refactoring for comparison. Template and style files are optional—the tool will extract inline templates/styles from the TypeScript file when not provided #### `diff_component_contract` **Purpose**: Compares before/after contracts to identify breaking changes diff --git a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/build-component-contract.tool.ts b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/build-component-contract.tool.ts index 24a11d9..b536428 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/build-component-contract.tool.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/build-component-contract.tool.ts @@ -10,8 +10,8 @@ import { resolveCrossPlatformPath } from '../../shared/utils/cross-platform-path interface BuildComponentContractOptions extends BaseHandlerOptions { saveLocation: string; - templateFile: string; - styleFile: string; + templateFile?: string; + styleFile?: string; typescriptFile: string; dsComponentName?: string; } @@ -30,13 +30,20 @@ export const buildComponentContractHandler = createHandler< dsComponentName = '', } = params; - const effectiveTemplatePath = resolveCrossPlatformPath(cwd, templateFile); - const effectiveScssPath = resolveCrossPlatformPath(cwd, styleFile); const effectiveTypescriptPath = resolveCrossPlatformPath( cwd, typescriptFile, ); + // If templateFile or styleFile are not provided, use the TypeScript file path + // This indicates inline template/styles + const effectiveTemplatePath = templateFile + ? resolveCrossPlatformPath(cwd, templateFile) + : effectiveTypescriptPath; + const effectiveScssPath = styleFile + ? resolveCrossPlatformPath(cwd, styleFile) + : effectiveTypescriptPath; + const contract = await buildComponentContract( effectiveTemplatePath, effectiveScssPath, diff --git a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/models/schema.ts b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/models/schema.ts index 0dc5f7f..fdcb860 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/models/schema.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/models/schema.ts @@ -19,12 +19,12 @@ export const buildComponentContractSchema: ToolSchemaOptions = { templateFile: { type: 'string', description: - 'Path to the component template file (.html) or TypeScript component file (.ts) for inline templates. Supports both absolute and relative paths.', + 'Path to the component template file (.html). Optional - if not provided or if the path matches typescriptFile, will extract inline template from the component. Supports both absolute and relative paths.', }, styleFile: { type: 'string', description: - 'Path to the component style file (.scss, .sass, .less, .css). Supports both absolute and relative paths.', + 'Path to the component style file (.scss, .sass, .less, .css). Optional - if not provided or if the path matches typescriptFile, will extract inline styles from the component. Supports both absolute and relative paths.', }, typescriptFile: { type: 'string', @@ -37,7 +37,7 @@ export const buildComponentContractSchema: ToolSchemaOptions = { default: '', }, }, - required: ['saveLocation', 'templateFile', 'styleFile', 'typescriptFile'], + required: ['saveLocation', 'typescriptFile'], }, annotations: { title: 'Build Component Contract', diff --git a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/utils/build-contract.ts b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/utils/build-contract.ts index dd89048..4a3c7f0 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/utils/build-contract.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/utils/build-contract.ts @@ -11,7 +11,8 @@ import type { ComponentContract } from '../../shared/models/types.js'; import { relative } from 'node:path'; /** - * Build a complete component contract from template and style files + * Build a complete component contract from template and style files. + * Template and style paths can be the same as TypeScript path for inline templates/styles. */ export async function buildComponentContract( templatePath: string, @@ -19,32 +20,41 @@ export async function buildComponentContract( cwd: string, typescriptPath: string, ): Promise { - const resolvedScssPath = resolveCrossPlatformPathAndValidate(cwd, scssPath); - const resolvedTemplatePath = resolveCrossPlatformPathAndValidate( - cwd, - templatePath, - ); const componentTsPath = resolveCrossPlatformPathAndValidate( cwd, typescriptPath, ); - const requiredPaths = [ - { path: resolvedScssPath, name: 'Style file' }, - { path: resolvedTemplatePath, name: 'Template file' }, - { path: componentTsPath, name: 'Component TypeScript file' }, - ]; + // Validate TypeScript file exists (required) + if (!existsSync(componentTsPath)) { + throw new Error(`Component TypeScript file not found: ${componentTsPath}`); + } + + // Resolve and validate template path + // If it's the same as TS path, it means inline template + const resolvedTemplatePath = resolveCrossPlatformPathAndValidate( + cwd, + templatePath, + ); + const isInlineTemplate = resolvedTemplatePath === componentTsPath; + + if (!isInlineTemplate && !existsSync(resolvedTemplatePath)) { + throw new Error(`Template file not found: ${resolvedTemplatePath}`); + } + + // Resolve and validate style path + // If it's the same as TS path, it means inline styles or no external styles + const resolvedScssPath = resolveCrossPlatformPathAndValidate(cwd, scssPath); + const isInlineOrNoStyles = resolvedScssPath === componentTsPath; - for (const { path, name } of requiredPaths) { - if (!existsSync(path)) { - throw new Error(`${name} not found: ${path}`); - } + if (!isInlineOrNoStyles && !existsSync(resolvedScssPath)) { + throw new Error(`Style file not found: ${resolvedScssPath}`); } const sources = { ts: readFileSync(componentTsPath, 'utf-8'), - scss: readFileSync(resolvedScssPath, 'utf-8'), - template: readFileSync(resolvedTemplatePath, 'utf-8'), + scss: isInlineOrNoStyles ? '' : readFileSync(resolvedScssPath, 'utf-8'), + template: isInlineTemplate ? '' : readFileSync(resolvedTemplatePath, 'utf-8'), }; const [parsedComponent] = await parseComponents([componentTsPath]); @@ -55,28 +65,22 @@ export async function buildComponentContract( const relativeTemplatePath = relative(cwd, resolvedTemplatePath); const relativeScssPath = relative(cwd, resolvedScssPath); - const meta = generateMeta(relativeTemplatePath, parsedComponent, false); + const meta = generateMeta(relativeTemplatePath, parsedComponent, isInlineTemplate); const publicApi = extractPublicApi(parsedComponent); const { slots, dom } = await extractSlotsAndDom(parsedComponent); - // ------------------------------------------------------------------------- - // Collect styles from both external SCSS and inline `styles` array - // ------------------------------------------------------------------------- const styleBuckets: import('../../shared/models/types.js').StyleDeclarations[] = []; - // External stylesheet – only if it is a real stylesheet different from the TS file - if (resolvedScssPath !== componentTsPath) { + if (!isInlineOrNoStyles) { const externalStyles = await collectStylesV2(resolvedScssPath, dom); externalStyles.sourceFile = relativeScssPath; styleBuckets.push(externalStyles); } - // Inline styles declared on the component decorator const inlineStyles = await collectInlineStyles(parsedComponent, dom); styleBuckets.push(inlineStyles); - // Merge collected buckets giving later buckets precedence on selector clashes const styles = styleBuckets.reduce< import('../../shared/models/types.js').StyleDeclarations >( From ad472453f6b79e517eaa753da846ab3e92b3a48c Mon Sep 17 00:00:00 2001 From: Kirill Date: Thu, 2 Oct 2025 18:05:22 +0300 Subject: [PATCH 2/2] fix: fix format issues --- .../component-contract/builder/utils/build-contract.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/utils/build-contract.ts b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/utils/build-contract.ts index 4a3c7f0..348ecf1 100644 --- a/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/utils/build-contract.ts +++ b/packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/utils/build-contract.ts @@ -54,7 +54,9 @@ export async function buildComponentContract( const sources = { ts: readFileSync(componentTsPath, 'utf-8'), scss: isInlineOrNoStyles ? '' : readFileSync(resolvedScssPath, 'utf-8'), - template: isInlineTemplate ? '' : readFileSync(resolvedTemplatePath, 'utf-8'), + template: isInlineTemplate + ? '' + : readFileSync(resolvedTemplatePath, 'utf-8'), }; const [parsedComponent] = await parseComponents([componentTsPath]); @@ -65,7 +67,11 @@ export async function buildComponentContract( const relativeTemplatePath = relative(cwd, resolvedTemplatePath); const relativeScssPath = relative(cwd, resolvedScssPath); - const meta = generateMeta(relativeTemplatePath, parsedComponent, isInlineTemplate); + const meta = generateMeta( + relativeTemplatePath, + parsedComponent, + isInlineTemplate, + ); const publicApi = extractPublicApi(parsedComponent); const { slots, dom } = await extractSlotsAndDom(parsedComponent);