Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 736c88d

Browse files
feat(@angular-devkit/schematics): add schematics to generate ai context files.
* `ng generate config ai` to prompt support tools. * `ng generate config ai --tool=gemini` to specify the tool. Supported ai tools: gemini, claude, copilot, windsurf, cursor.
1 parent 5d085ee commit 736c88d

File tree

7 files changed

+156
-3
lines changed

7 files changed

+156
-3
lines changed

‎packages/angular_devkit/schematics/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export * from './rules/base';
2323
export * from './rules/call';
2424
export * from './rules/move';
2525
export * from './rules/random';
26+
export * from './rules/rename';
2627
export * from './rules/schematic';
2728
export * from './rules/template';
2829
export * from './rules/url';

‎packages/angular_devkit/schematics/src/rules/move.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { join, normalize } from '@angular-devkit/core';
1010
import { Rule } from '../engine/interface';
1111
import { noop } from './base';
1212

13+
export function move(from: string, to: string): Rule;
14+
export function move(to: string): Rule;
1315
export function move(from: string, to?: string): Rule {
1416
if (to === undefined) {
1517
to = from;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import { posix as path } from 'node:path';
10+
import { Rule } from '../engine';
11+
12+
export function rename(filePath: string, fromFileName: string, fileName: string): Rule {
13+
return (tree) => {
14+
tree.rename(path.join(filePath, fromFileName), path.join(filePath, fileName));
15+
16+
return tree;
17+
};
18+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
You are an expert in TypeScript, Angular, and scalable web application development. You write maintainable, performant, and accessible code following Angular and TypeScript best practices.
2+
## TypeScript Best Practices
3+
- Use strict type checking
4+
- Prefer type inference when the type is obvious
5+
- Avoid the `any` type; use `unknown` when type is uncertain
6+
## Angular Best Practices
7+
- Always use standalone components over NgModules
8+
- Do NOT set `standalone: true` inside the `@Component`, `@Directive` and `@Pipe` decorators
9+
- Use signals for state management
10+
- Implement lazy loading for feature routes
11+
- Use `NgOptimizedImage` for all static images.
12+
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
13+
## Components
14+
- Keep components small and focused on a single responsibility
15+
- Use `input()` and `output()` functions instead of decorators
16+
- Use `computed()` for derived state
17+
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
18+
- Prefer inline templates for small components
19+
- Prefer Reactive forms instead of Template-driven ones
20+
- Do NOT use `ngClass`, use `class` bindings instead
21+
- DO NOT use `ngStyle`, use `style` bindings instead
22+
## State Management
23+
- Use signals for local component state
24+
- Use `computed()` for derived state
25+
- Keep state transformations pure and predictable
26+
- Do NOT use `mutate` on signals, use `update` or `set` instead
27+
## Templates
28+
- Keep templates simple and avoid complex logic
29+
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
30+
- Use the async pipe to handle observables
31+
## Services
32+
- Design services around a single responsibility
33+
- Use the `providedIn: 'root'` option for singleton services
34+
- Use the `inject()` function instead of constructor injection

‎packages/schematics/angular/config/index.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
filter,
1515
mergeWith,
1616
move,
17+
rename,
1718
strings,
1819
url,
1920
} from '@angular-devkit/schematics';
@@ -30,6 +31,8 @@ export default function (options: ConfigOptions): Rule {
3031
return addKarmaConfig(options);
3132
case ConfigType.Browserslist:
3233
return addBrowserslistConfig(options);
34+
case ConfigType.Ai:
35+
return addAiContextFile(options);
3336
default:
3437
throw new SchematicsException(`"${options.type}" is an unknown configuration file type.`);
3538
}
@@ -103,3 +106,53 @@ function addKarmaConfig(options: ConfigOptions): Rule {
103106
);
104107
});
105108
}
109+
110+
function addAiContextFile(options: ConfigOptions): Rule {
111+
let fileName: string;
112+
let filePath: string;
113+
switch (options.tool) {
114+
case 'gemini':
115+
fileName = 'GEMINI.md';
116+
filePath = '.gemini';
117+
break;
118+
case 'copilot':
119+
fileName = 'copilot-instructions.md';
120+
filePath = '.github';
121+
break;
122+
case 'claude':
123+
fileName = 'CLAUDE.md';
124+
filePath = '.claude';
125+
break;
126+
case 'cursor':
127+
fileName = 'cursor.md';
128+
filePath = path.join('.cursor', 'rules');
129+
break;
130+
case 'windsurf':
131+
fileName = 'guidelines.md';
132+
filePath = path.join('.windsurf', 'rules');
133+
break;
134+
default:
135+
throw new SchematicsException(`Unsupported AI tool: ${options.tool}`);
136+
}
137+
138+
return async (host) => {
139+
const workspace = await readWorkspace(host);
140+
const project = workspace.projects.get(options.project);
141+
if (!project) {
142+
throw new SchematicsException(`Project name "${options.project}" doesn't not exist.`);
143+
}
144+
145+
// Keep this file in sync with the one presented here https://angular.dev/ai/develop-with-ai
146+
// The file is located in the framework repo at adev/src/context/best-practices.md
147+
const templateName = 'best-practices.md';
148+
149+
return mergeWith(
150+
apply(url('./files'), [
151+
filter((p) => p.endsWith(`${templateName}.template`)),
152+
applyTemplates({}),
153+
move(filePath),
154+
rename(filePath, templateName, fileName),
155+
]),
156+
);
157+
};
158+
}

‎packages/schematics/angular/config/index_spec.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
1010
import { Schema as ApplicationOptions } from '../application/schema';
1111
import { Schema as WorkspaceOptions } from '../workspace/schema';
12-
import { Schema as ConfigOptions, Type as ConfigType } from './schema';
12+
import { Schema as ConfigOptions, ToolasConfigTool,Type as ConfigType } from './schema';
1313

1414
describe('Config Schematic', () => {
1515
const schematicRunner = new SchematicTestRunner(
@@ -32,12 +32,15 @@ describe('Config Schematic', () => {
3232
};
3333

3434
let applicationTree: UnitTestTree;
35-
function runConfigSchematic(type: ConfigType): Promise<UnitTestTree> {
35+
function runConfigSchematic(type: ConfigType, tool?: ConfigTool): Promise<UnitTestTree>;
36+
function runConfigSchematic(type: ConfigType.Ai, tool: ConfigTool): Promise<UnitTestTree>;
37+
function runConfigSchematic(type: ConfigType, tool?: ConfigTool): Promise<UnitTestTree> {
3638
return schematicRunner.runSchematic<ConfigOptions>(
3739
'config',
3840
{
3941
project: 'foo',
4042
type,
43+
tool,
4144
},
4245
applicationTree,
4346
);
@@ -97,4 +100,37 @@ describe('Config Schematic', () => {
97100
expect(tree.readContent('projects/foo/.browserslistrc')).toContain('Chrome >=');
98101
});
99102
});
103+
104+
describe(`when 'type' is 'ai'`, () => {
105+
it('should create a GEMINI.MD file', async () => {
106+
const tree = await runConfigSchematic(ConfigType.Ai, ConfigTool.Gemini);
107+
expect(tree.readContent('.gemini/GEMINI.md')).toContain('You are an expert in TypeScript');
108+
});
109+
110+
it('should create a copilot-instructions.md file', async () => {
111+
const tree = await runConfigSchematic(ConfigType.Ai, ConfigTool.Copilot);
112+
expect(tree.readContent('.github/copilot-instructions.md')).toContain(
113+
'You are an expert in TypeScript',
114+
);
115+
});
116+
117+
it('should create a cursor file', async () => {
118+
const tree = await runConfigSchematic(ConfigType.Ai, ConfigTool.Cursor);
119+
expect(tree.readContent('.cursor/rules/cursor.md')).toContain(
120+
'You are an expert in TypeScript',
121+
);
122+
});
123+
124+
it('should create a windsurf file', async () => {
125+
const tree = await runConfigSchematic(ConfigType.Ai, ConfigTool.Windsurf);
126+
expect(tree.readContent('.windsurf/rules/guidelines.md')).toContain(
127+
'You are an expert in TypeScript',
128+
);
129+
});
130+
131+
it('should create a claude file', async () => {
132+
const tree = await runConfigSchematic(ConfigType.Ai, ConfigTool.Claude);
133+
expect(tree.readContent('.claude/CLAUDE.md')).toContain('You are an expert in TypeScript');
134+
});
135+
});
100136
});

‎packages/schematics/angular/config/schema.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,21 @@
1616
"type": {
1717
"type": "string",
1818
"description": "Specifies the type of configuration file to generate.",
19-
"enum": ["karma", "browserslist"],
19+
"enum": ["karma", "browserslist", "ai"],
2020
"x-prompt": "Which type of configuration file would you like to create?",
2121
"$default": {
2222
"$source": "argv",
2323
"index": 0
2424
}
25+
},
26+
"tool": {
27+
"type": "string",
28+
"description": "Specifies the AI tool to configure when type is 'ai'.",
29+
"enum": ["gemini", "copilot", "claude", "cursor", "windsurf"],
30+
"x-prompt": {
31+
"message": "Which AI tool would you like to configure?",
32+
"when": "type === 'ai'"
33+
}
2534
}
2635
},
2736
"required": ["project", "type"]

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /