From 133b76bd62a5db34a5839fae2305f5cf37be3037 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 30 Sep 2025 11:30:55 -0400 Subject: [PATCH] fix(@schematics/angular): make ai-config schematic non-destructive The ai-config schematic will now check if a configuration file already exists before generating a new one. If a file is present, the schematic will skip it and log a detailed, actionable warning to the console. This change prevents the accidental overwriting of user-customized configuration files, making the schematic safer to run multiple times. The warning message informs the user why the file was skipped and instructs them on how to regenerate it if they wish to revert to the default settings. A unit test is included to verify this non-destructive behavior. --- .../schematics/angular/ai-config/index.ts | 54 ++++++++++++------- .../angular/ai-config/index_spec.ts | 22 ++++++++ 2 files changed, 57 insertions(+), 19 deletions(-) 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();