Browse Source

Refactor ai-config to use comma-separated tool string

Updated the ai-config schematic to accept a comma-separated string for the 'tool' option instead of an array. Adjusted validation, parsing, and schema prompts accordingly for improved CLI usability and flexibility.
pull/23910/head
Fahri Gedik 12 months ago
parent
commit
73a8beba67
  1. 51
      npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts
  2. 2
      npm/ng-packs/packages/schematics/src/commands/ai-config/model.ts
  3. 22
      npm/ng-packs/packages/schematics/src/commands/ai-config/schema.json

51
npm/ng-packs/packages/schematics/src/commands/ai-config/index.ts

@ -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',

2
npm/ng-packs/packages/schematics/src/commands/ai-config/model.ts

@ -1,7 +1,7 @@
export type AiTool = 'claude' | 'copilot' | 'cursor' | 'gemini' | 'junie' | 'windsurf';
export interface AiConfigSchema {
tool: AiTool[];
tool?: string;
targetProject?: string;
overwrite?: boolean;
}

22
npm/ng-packs/packages/schematics/src/commands/ai-config/schema.json

@ -5,25 +5,11 @@
"type": "object",
"properties": {
"tool": {
"description": "Specifies which AI tools to generate configuration files for",
"type": "array",
"items": {
"type": "string",
"enum": ["claude", "copilot", "cursor", "gemini", "junie", "windsurf"]
},
"default": [],
"description": "Comma-separated list of AI tools (e.g., claude,cursor,gemini)",
"type": "string",
"x-prompt": {
"message": "Which AI tools would you like to generate configuration files for?",
"type": "list",
"multiselect": true,
"items": [
{ "value": "claude", "label": "Claude (Anthropic) - AI assistant with deep code understanding" },
{ "value": "copilot", "label": "GitHub Copilot - AI pair programmer" },
{ "value": "cursor", "label": "Cursor - AI-first code editor" },
{ "value": "gemini", "label": "Google Gemini - AI assistant by Google" },
{ "value": "junie", "label": "Junie AI - Development assistant" },
{ "value": "windsurf", "label": "Windsurf - AI development environment" }
]
"message": "Which AI tools would you like to generate configuration files for? (comma-separated)",
"type": "input"
}
},
"targetProject": {

Loading…
Cancel
Save