diff --git a/packages/schematics/angular/ai-config/index.ts b/packages/schematics/angular/ai-config/index.ts index 4d234b35e5d4..797065c50df0 100644 --- a/packages/schematics/angular/ai-config/index.ts +++ b/packages/schematics/angular/ai-config/index.ts @@ -55,25 +55,41 @@ interface ContextFileInfo { } export default function ({ tool }: ConfigOptions): Rule { - if (!tool) { - return noop(); - } + return (tree, context) => { + if (!tool) { + return noop(); + } - const rules = tool - .filter((tool) => tool !== Tool.None) - .map((selectedTool) => AI_TOOLS[selectedTool]) - .map(({ rulesName, directory, frontmatter }) => - mergeWith( - apply(url('./files'), [ - applyTemplates({ - ...strings, - rulesName, - frontmatter, - }), - move(directory), - ]), - ), - ); + const rules = tool + .filter((tool) => tool !== Tool.None) + .map((selectedTool) => { + const { rulesName, directory, frontmatter } = AI_TOOLS[selectedTool]; + const path = `${directory}/${rulesName}`; - return chain(rules); + if (tree.exists(path)) { + const toolName = strings.classify(selectedTool); + context.logger.warn( + `Skipping configuration file for '${toolName}' at '${path}' because it already exists.\n` + + 'This is to prevent overwriting a potentially customized file. ' + + 'If you want to regenerate it with Angular recommended defaults, please delete the existing file and re-run the command.\n' + + 'You can review the latest recommendations at https://angular.dev/ai/develop-with-ai.', + ); + + return noop(); + } + + return mergeWith( + apply(url('./files'), [ + applyTemplates({ + ...strings, + rulesName, + frontmatter, + }), + move(directory), + ]), + ); + }); + + return chain(rules); + }; } diff --git a/packages/schematics/angular/ai-config/index_spec.ts b/packages/schematics/angular/ai-config/index_spec.ts index d21186be408a..e1293454ed19 100644 --- a/packages/schematics/angular/ai-config/index_spec.ts +++ b/packages/schematics/angular/ai-config/index_spec.ts @@ -78,6 +78,28 @@ describe('Ai Config Schematic', () => { expect(tree.files.length).toBe(filesCount); }); + it('should not overwrite an existing file', async () => { + const customContent = 'custom user content'; + workspaceTree.create('.gemini/GEMINI.md', customContent); + + const messages: string[] = []; + const loggerSubscription = schematicRunner.logger.subscribe((x) => messages.push(x.message)); + + try { + const tree = await runConfigSchematic([ConfigTool.Gemini]); + + expect(tree.readContent('.gemini/GEMINI.md')).toBe(customContent); + expect(messages).toContain( + `Skipping configuration file for 'Gemini' at '.gemini/GEMINI.md' because it already exists.\n` + + 'This is to prevent overwriting a potentially customized file. ' + + 'If you want to regenerate it with Angular recommended defaults, please delete the existing file and re-run the command.\n' + + 'You can review the latest recommendations at https://angular.dev/ai/develop-with-ai.', + ); + } finally { + loggerSubscription.unsubscribe(); + } + }); + it('should create for tool if None and Gemini are selected', async () => { const tree = await runConfigSchematic([ConfigTool.Gemini, ConfigTool.None]); expect(tree.exists('.gemini/GEMINI.md')).toBeTruthy();