Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/component-refactoring-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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`
Expand Down
12 changes: 7 additions & 5 deletions docs/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 4 additions & 2 deletions docs/ds-refactoring-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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`
Expand Down
10 changes: 5 additions & 5 deletions docs/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -37,7 +37,7 @@ export const buildComponentContractSchema: ToolSchemaOptions = {
default: '',
},
},
required: ['saveLocation', 'templateFile', 'styleFile', 'typescriptFile'],
required: ['saveLocation', 'typescriptFile'],
},
annotations: {
title: 'Build Component Contract',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,52 @@ 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,
scssPath: string,
cwd: string,
typescriptPath: string,
): Promise<ComponentContract> {
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]);
Expand All @@ -55,28 +67,26 @@ 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
>(
Expand Down