Skip to content

Commit e304821

Browse files
committed
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.
1 parent 7c1ef7f commit e304821

File tree

2 files changed

+57
-19
lines changed

2 files changed

+57
-19
lines changed

packages/schematics/angular/ai-config/index.ts

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -55,25 +55,41 @@ interface ContextFileInfo {
5555
}
5656

5757
export default function ({ tool }: ConfigOptions): Rule {
58-
if (!tool) {
59-
return noop();
60-
}
58+
return (tree, context) => {
59+
if (!tool) {
60+
return noop();
61+
}
6162

62-
const rules = tool
63-
.filter((tool) => tool !== Tool.None)
64-
.map((selectedTool) => AI_TOOLS[selectedTool])
65-
.map(({ rulesName, directory, frontmatter }) =>
66-
mergeWith(
67-
apply(url('./files'), [
68-
applyTemplates({
69-
...strings,
70-
rulesName,
71-
frontmatter,
72-
}),
73-
move(directory),
74-
]),
75-
),
76-
);
63+
const rules = tool
64+
.filter((tool) => tool !== Tool.None)
65+
.map((selectedTool) => {
66+
const { rulesName, directory, frontmatter } = AI_TOOLS[selectedTool];
67+
const path = `${directory}/${rulesName}`;
7768

78-
return chain(rules);
69+
if (tree.exists(path)) {
70+
const toolName = strings.classify(selectedTool);
71+
context.logger.warn(
72+
`Skipping configuration file for '${toolName}' at '${path}' because it already exists.\n` +
73+
'This is to prevent overwriting a potentially customized file. ' +
74+
'If you want to regenerate it with Angular recommended defaults, please delete the existing file and re-run the command.\n' +
75+
'You can review the latest recommendations at https://angular.dev/ai/develop-with-ai.',
76+
);
77+
78+
return noop();
79+
}
80+
81+
return mergeWith(
82+
apply(url('./files'), [
83+
applyTemplates({
84+
...strings,
85+
rulesName,
86+
frontmatter,
87+
}),
88+
move(directory),
89+
]),
90+
);
91+
});
92+
93+
return chain(rules);
94+
};
7995
}

packages/schematics/angular/ai-config/index_spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,28 @@ describe('Ai Config Schematic', () => {
7878
expect(tree.files.length).toBe(filesCount);
7979
});
8080

81+
it('should not overwrite an existing file', async () => {
82+
const customContent = 'custom user content';
83+
workspaceTree.create('.gemini/GEMINI.md', customContent);
84+
85+
const messages: string[] = [];
86+
const loggerSubscription = schematicRunner.logger.subscribe((x) => messages.push(x.message));
87+
88+
try {
89+
const tree = await runConfigSchematic([ConfigTool.Gemini]);
90+
91+
expect(tree.readContent('.gemini/GEMINI.md')).toBe(customContent);
92+
expect(messages).toContain(
93+
`Skipping configuration file for 'Gemini' at '.gemini/GEMINI.md' because it already exists.\n` +
94+
'This is to prevent overwriting a potentially customized file. ' +
95+
'If you want to regenerate it with Angular recommended defaults, please delete the existing file and re-run the command.\n' +
96+
'You can review the latest recommendations at https://angular.dev/ai/develop-with-ai.',
97+
);
98+
} finally {
99+
loggerSubscription.unsubscribe();
100+
}
101+
});
102+
81103
it('should create for tool if None and Gemini are selected', async () => {
82104
const tree = await runConfigSchematic([ConfigTool.Gemini, ConfigTool.None]);
83105
expect(tree.exists('.gemini/GEMINI.md')).toBeTruthy();

0 commit comments

Comments
 (0)