@ -3,23 +3,40 @@ import { join, normalize } from '@angular-devkit/core';
import { AiConfigSchema , AiTool } from './model' ;
import { getWorkspace } from '../../utils' ;
/ * *
* Generates AI configuration files for Angular projects based on selected tools .
* This schematic creates configuration files that help AI tools follow Angular best practices .
* /
export default function ( options : AiConfigSchema ) : Rule {
return async ( tree : Tree ) = > {
// Validate options
if ( ! options . tool || options . tool . length === 0 ) {
if ( ! options . tool || options . tool . trim ( ) === '' ) {
console . log ( 'ℹ️ No AI tools selected. Skipping configuration generation.' ) ;
console . log ( '' ) ;
console . log ( '💡 Usage examples:' ) ;
console . log ( ' ng g @abp/ng.schematics:ai-config --tool=claude,cursor' ) ;
console . log ( ' ng g @abp/ng.schematics:ai-config --tool=gemini --target-project=my-app' ) ;
console . log ( '' ) ;
console . log ( 'Available tools: claude, copilot, cursor, gemini, junie, windsurf' ) ;
return tree ;
}
const tools = options . tool
. split ( /[,\s]+/ )
. map ( t = > t . trim ( ) )
. filter ( t = > t ) as AiTool [ ] ;
const validTools : AiTool [ ] = [ 'claude' , 'copilot' , 'cursor' , 'gemini' , 'junie' , 'windsurf' ] ;
const invalidTools = tools . filter ( tool = > ! validTools . includes ( tool ) ) ;
if ( invalidTools . length > 0 ) {
throw new SchematicsException (
` Invalid AI tool(s): ${ invalidTools . join ( ', ' ) } . Valid options are: ${ validTools . join ( ', ' ) } `
) ;
}
if ( tools . length === 0 ) {
console . log ( 'ℹ️ No AI tools selected. Skipping configuration generation.' ) ;
return tree ;
}
// Get workspace configuration
const workspace = await getWorkspace ( tree ) ;
let targetPath = '/' ;
// If targetProject is specified, generate in project directory
if ( options . targetProject ) {
const project = workspace . projects . get ( options . targetProject ) ;
if ( ! project ) {
@ -32,20 +49,18 @@ export default function (options: AiConfigSchema): Rule {
console . log ( '🚀 Generating AI configuration files...' ) ;
console . log ( ` 📁 Target path: ${ targetPath } ` ) ;
console . log ( ` 🤖 Selected tools: ${ options . tool . join ( ', ' ) } ` ) ;
console . log ( ` 🤖 Selected tools: ${ tools . join ( ', ' ) } ` ) ;
// Generate rules for each selected tool
const rules : Rule [ ] = options . tool
const rules : Rule [ ] = tools
. map ( tool = > generateConfigForTool ( tool , targetPath , options . overwrite || false ) ) ;
// Apply all rules and log results
return chain ( [
. . . rules ,
( tree : Tree ) = > {
console . log ( '✅ AI configuration files generated successfully!' ) ;
console . log ( '\n📝 Generated files:' ) ;
options . tool . forEach ( tool = > {
tools . forEach ( tool = > {
const configPath = getConfigPath ( tool , targetPath ) ;
console . log ( ` - ${ configPath } ` ) ;
} ) ;
@ -58,37 +73,27 @@ export default function (options: AiConfigSchema): Rule {
} ;
}
/ * *
* Generates configuration for a specific AI tool
* /
function generateConfigForTool ( tool : AiTool , targetPath : string , overwrite : boolean ) : Rule {
return ( tree : Tree ) = > {
const configPath = getConfigPath ( tool , targetPath ) ;
// Check if file already exists
if ( tree . exists ( configPath ) && ! overwrite ) {
console . log ( ` ⚠️ Configuration file already exists: ${ configPath } ` ) ;
console . log ( ` Use --overwrite flag to replace existing files. ` ) ;
return tree ;
}
// Get template source
const sourceDir = ` ./files/ ${ tool } ` ;
const source = apply ( url ( sourceDir ) , [
filter ( path = > {
// Filter out any unnecessary files
return ! path . endsWith ( '.DS_Store' ) ;
} )
] ) ;
// Merge with existing tree
return mergeWith ( source , overwrite ? MergeStrategy.Overwrite : MergeStrategy.Default ) ;
} ;
}
/ * *
* Gets the configuration file path for a specific tool
* /
function getConfigPath ( tool : AiTool , basePath : string ) : string {
const configFiles : Record < AiTool , string > = {
claude : '.claude/CLAUDE.md' ,